พูลค่าคงที่ของสตริงของ Java อยู่ที่ไหนฮีปหรือสแต็ก


104

ฉันรู้แนวคิดของพูลค่าคงที่และพูลค่าคงที่สตริงที่ JVM ใช้เพื่อจัดการกับตัวอักษร String แต่ฉันไม่รู้ว่า JVM ใช้หน่วยความจำประเภทใดเพื่อเก็บค่าคงที่ของสตริง กองหรือกอง? เนื่องจากเป็นลิเทอรัลที่ไม่เกี่ยวข้องกับอินสแตนซ์ใด ๆ ฉันจึงถือว่ามันจะถูกเก็บไว้ในสแต็ก แต่ถ้ามันไม่ได้อ้างถึงโดยอินสแตนซ์ใด ๆ GC จะต้องรวบรวมลิเทอรัล (แก้ไขฉันถ้าฉันผิด) ดังนั้นจะจัดการอย่างไรหากเก็บไว้ในสแต็ก


11
จะเก็บพูลไว้บนสแตกได้อย่างไร? คุณรู้แนวคิดของสแต็กหรือไม่?
The Scrum Meister

1
สวัสดี Scrum Meister ฉันพยายามหมายความว่ามันเป็นไปไม่ได้ ขออภัยที่ผิดคอนเวนชั่น เกี่ยวกับ GC เมื่อกี้ฉันมารู้ ขอบคุณสำหรับสิ่งนั้น
Rengasami Ramanujam

@TheScrumMeister - ในความเป็นจริงภายใต้สถานการณ์บางอย่างพวกเขาสามารถเก็บขยะได้ "ดีลเบรกเกอร์" คืออ็อบเจ็กต์โค้ดสำหรับคลาสใด ๆ ที่กล่าวถึงสตริงลิเทอรัลจะมีการอ้างอิงถึงอ็อบเจกต์ String ที่แสดงถึงลิเทอรัล
Stephen C

คำตอบ:


74

คำตอบคือไม่มีทางเทคนิค ตามที่ Java เครื่องเสมือนจำเพาะพื้นที่สำหรับการจัดเก็บสายอักขระตัวอักษรที่อยู่ในสระว่ายน้ำคงที่รันไทม์ พื้นที่หน่วยความจำพูลคงที่รันไทม์ถูกจัดสรรตามพื้นฐานต่อคลาสหรือต่ออินเทอร์เฟซดังนั้นจึงไม่เชื่อมโยงกับอินสแตนซ์ออบเจ็กต์ใด ๆ เลย พูลค่าคงที่รันไทม์เป็นส่วนย่อยของพื้นที่วิธีการที่ "เก็บโครงสร้างต่อคลาสเช่นพูลค่าคงที่รันไทม์ข้อมูลฟิลด์และเมธอดและโค้ดสำหรับเมธอดและคอนสตรัคเตอร์รวมถึงเมธอดพิเศษที่ใช้ในคลาสและอินสแตนซ์การเริ่มต้นและอินเทอร์เฟซ พิมพ์ initialization ". ข้อมูลจำเพาะ VM ระบุว่าแม้ว่าพื้นที่วิธีการ เป็นส่วนหนึ่งของฮีปตามหลักเหตุผลไม่ได้กำหนดว่าหน่วยความจำที่จัดสรรในพื้นที่วิธีการจะต้องอยู่ภายใต้การรวบรวมขยะหรือพฤติกรรมอื่น ๆ ที่จะเชื่อมโยงกับโครงสร้างข้อมูลปกติที่จัดสรรให้กับฮีป


8
ที่จริงแล้วเมื่อโหลดคลาสใน VM ค่าคงที่ของสตริงจะถูกคัดลอกไปยังฮีปไปยังพูลสตริงแบบกว้าง VM (ใน Permgen ตามที่ Stephen C กล่าว) เนื่องจากตัวอักษรสตริงที่เท่ากันในคลาสต่างๆจะต้องเป็น วัตถุสตริงเดียวกัน (โดย JLS)
Paŭlo Ebermann

1
ขอบคุณทุกท่านสำหรับคำตอบ ฉันเข้าใจมากกับการสนทนานี้
ดีใจที่ได้

4
Paŭloซึ่งเป็นจริงสำหรับเครื่องเสมือนของ Sun แต่ไม่จำเป็นต้องเป็นจริงสำหรับการใช้งาน JVM ทั้งหมด ตามที่ระบุไว้ใน JVM แม้ว่าพูลค่าคงที่รันไทม์และพื้นที่เมธอดจะเป็นส่วนหนึ่งของฮีปในเชิงตรรกะ แต่ก็ไม่จำเป็นต้องมีลักษณะการทำงานเหมือนกัน ความแตกต่างทางความหมายเพียงเล็กน้อยจริงๆ :)
Duane Moore


54

ตามที่อธิบายโดยคำตอบนี้ไม่ได้ระบุตำแหน่งที่แน่นอนของพูลสตริงและอาจแตกต่างกันไปในการใช้งาน JVM หนึ่งไปยังอีก

เป็นที่น่าสนใจที่จะทราบว่าจนถึง Java 7 พูลอยู่ในพื้นที่ Permgen ของฮีปบนฮอตสปอต JVM แต่ถูกย้ายไปที่ส่วนหลักของฮีปตั้งแต่ Java 7 :

พื้นที่ : HotSpot
เรื่องย่อ : ใน JDK 7 สตริงภายในจะไม่ได้รับการจัดสรรในการสร้างฮีป Java แบบถาวรอีกต่อไป แต่จะถูกจัดสรรในส่วนหลักของฮีป Java (ที่เรียกว่าคนรุ่นใหม่และรุ่นเก่า) พร้อมกับอีกกลุ่มหนึ่ง วัตถุที่สร้างโดยแอปพลิเคชัน การเปลี่ยนแปลงนี้จะส่งผลให้มีข้อมูลอยู่ในฮีปหลักของ Java มากขึ้นและข้อมูลในการสร้างแบบถาวรน้อยลงและอาจต้องปรับขนาดฮีป แอปพลิเคชันส่วนใหญ่จะเห็นความแตกต่างเล็กน้อยในการใช้งานฮีปเนื่องจากการเปลี่ยนแปลงนี้ แต่แอปพลิเคชันขนาดใหญ่ที่โหลดคลาสจำนวนมากหรือใช้เมธอด String.intern () อย่างหนักจะเห็นความแตกต่างที่สำคัญกว่า RFE: 6962931

และใน Java 8 Hotspot การสร้างแบบถาวรได้ถูกลบออกทั้งหมด


30

ตัวอักษรสตริงจะไม่ถูกเก็บไว้ในสแต็ก ไม่เลย ในความเป็นจริงไม่มีการจัดเก็บวัตถุบนสแตก

ตัวอักษรของสตริง (หรือมากกว่าถูกต้องวัตถุสตริงที่เป็นตัวแทนของพวกเขา) จะถูกเก็บไว้ในอดีตกองเรียกว่า "PermGen" กอง (Permgen ย่อมาจากการสร้างแบบถาวร)

ภายใต้สถานการณ์ปกติตัวอักษร String และสิ่งอื่น ๆ ในฮีป Permgen สามารถเข้าถึงได้ "ถาวร" และจะไม่เก็บขยะ (ตัวอย่างเช่นสตริงลิเทอรัลสามารถเข้าถึงได้จากอ็อบเจ็กต์โค้ดที่ใช้อ็อบเจ็กต์) อย่างไรก็ตามคุณสามารถกำหนดค่า JVM เพื่อพยายามค้นหาและรวบรวมคลาสที่โหลดแบบไดนามิกซึ่งไม่จำเป็นอีกต่อไปและอาจทำให้สตริงลิเทอรัลถูกรวบรวมเป็นขยะ .

CLARIFICATION # 1 - ฉันไม่ได้บอกว่า Permgen ไม่ได้รับ GC'ed โดยทั่วไปแล้วเมื่อ JVM ตัดสินใจเรียกใช้ Full GC ประเด็นของฉันคือ String literalsจะสามารถเข้าถึงได้ตราบเท่าที่โค้ดที่ใช้นั้นสามารถเข้าถึงได้และโค้ดจะสามารถเข้าถึงได้ตราบใดที่ classloader ของโค้ดนั้นสามารถเข้าถึงได้และสำหรับ classloaders เริ่มต้นนั่นหมายถึง "ตลอดไป"

CLARIFICATION # 2 - ในความเป็นจริง Java 7 และใหม่กว่าใช้ฮีปปกติเพื่อเก็บสตริงพูล ดังนั้นออบเจ็กต์ String ที่แสดงถึงตัวอักษร String และสตริงภายในจึงอยู่ในฮีปปกติ (ดูคำตอบของ @ assylias สำหรับรายละเอียด)


แต่ฉันยังคงพยายามหาเส้นบาง ๆ ระหว่างการจัดเก็บตัวอักษรสตริงและสตริงที่สร้างด้วยnew.

ไม่มี "เส้นบาง ๆ " มันง่ายมาก:

  • String อ็อบเจ็กต์ที่แสดง / สอดคล้องกับตัวอักษรสตริงจะถูกเก็บไว้ในพูลสตริง
  • Stringอ็อบเจ็กต์ที่สร้างขึ้นโดยการString::internโทรจะถูกเก็บไว้ในสตริงพูล
  • Stringอบเจ็กต์อื่น ๆ ทั้งหมดไม่ได้อยู่ในกลุ่มสตริง

จากนั้นมีคำถามแยกต่างหากว่า "เก็บสตริงพูล" ไว้ที่ไหน ก่อนหน้า Java 7 เป็นฮีป Permgen ตั้งแต่ Java 7 เป็นต้นไปจะเป็นฮีปหลัก


23

การรวมสตริง

String pooling (บางครั้งเรียกอีกอย่างว่า string canonicalisation) เป็นกระบวนการแทนที่อ็อบเจกต์ String หลายตัวที่มีค่าเท่ากัน แต่มีเอกลักษณ์แตกต่างกันด้วยอ็อบเจ็กต์ String ที่ใช้ร่วมกันเดียว คุณสามารถบรรลุเป้าหมายนี้ได้โดยการรักษาแผนที่ของคุณเอง (อาจมีการอ้างอิงที่อ่อนหรืออ่อนลงขึ้นอยู่กับความต้องการของคุณ) และใช้ค่าแผนที่เป็นค่าที่ยอมรับได้ หรือคุณสามารถใช้วิธี String.intern () ที่ JDK จัดเตรียมให้คุณ

ในช่วงเวลาของ Java 6 ที่ใช้ String.intern () ถูกห้ามโดยหลายมาตรฐานเนื่องจากมีความเป็นไปได้สูงที่จะได้รับ OutOfMemoryException หากการรวมกลุ่มไม่สามารถควบคุมได้ การใช้งาน Oracle Java 7 ของสตริงพูลมีการเปลี่ยนแปลงอย่างมาก คุณสามารถดูรายละเอียดใน http://bugs.sun.com/view_bug.do?bug_id=6962931และ http://bugs.sun.com/view_bug.do?bug_id=6962930

String.intern () ใน Java 6

ในสมัยก่อนนั้นสตริงภายในทั้งหมดจะถูกเก็บไว้ใน PermGen - ส่วนที่มีขนาดคงที่ของฮีปส่วนใหญ่จะใช้สำหรับการจัดเก็บคลาสที่โหลดและพูลสตริง นอกเหนือจากสตริงภายในอย่างชัดเจนแล้วพูลสตริง PermGen ยังมีสตริงตัวอักษรทั้งหมดที่ใช้ก่อนหน้านี้ในโปรแกรมของคุณ (คำสำคัญที่นี่ถูกใช้ - ถ้าคลาสหรือเมธอดไม่เคยโหลด / เรียกค่าคงที่ที่กำหนดไว้ในนั้นจะไม่ถูกโหลด)

ปัญหาที่ใหญ่ที่สุดของกลุ่มสตริงใน Java 6 คือตำแหน่งของมัน - PermGen PermGen มีขนาดคงที่และไม่สามารถขยายได้เมื่อรันไทม์ คุณสามารถตั้งค่าได้โดยใช้ตัวเลือก -XX: MaxPermSize = 96m เท่าที่ฉันทราบขนาด PermGen เริ่มต้นจะแตกต่างกันไประหว่าง 32M ถึง 96M ขึ้นอยู่กับแพลตฟอร์ม คุณสามารถเพิ่มขนาดได้ แต่ขนาดจะยังคงที่ ข้อ จำกัด ดังกล่าวจำเป็นต้องใช้ String.intern อย่างระมัดระวัง - คุณไม่ควรฝึกการป้อนข้อมูลของผู้ใช้ที่ไม่มีการควบคุมโดยใช้วิธีนี้ นั่นเป็นเหตุผลที่การรวมสตริงในช่วงเวลาของ Java 6 ส่วนใหญ่ถูกนำไปใช้ในแผนที่ที่มีการจัดการด้วยตนเอง

String.intern () ใน Java 7

วิศวกรของ Oracle ทำการเปลี่ยนแปลงที่สำคัญอย่างยิ่งกับลอจิกการรวมสตริงใน Java 7 - กลุ่มสตริงถูกย้ายไปที่ฮีป หมายความว่าคุณไม่ถูก จำกัด ด้วยพื้นที่หน่วยความจำขนาดคงที่แยกต่างหากอีกต่อไป ตอนนี้สตริงทั้งหมดอยู่ในฮีปเช่นเดียวกับอ็อบเจ็กต์ธรรมดาอื่น ๆ ส่วนใหญ่ซึ่งช่วยให้คุณจัดการเฉพาะขนาดฮีปขณะปรับแต่งแอปพลิเคชันของคุณ ในทางเทคนิคสิ่งนี้เพียงอย่างเดียวอาจเป็นเหตุผลเพียงพอที่จะพิจารณาใหม่โดยใช้ String.intern () ในโปรแกรม Java 7 ของคุณ แต่มีเหตุผลอื่น ๆ

ค่าสตริงพูลเป็นขยะที่รวบรวม

ใช่สตริงทั้งหมดในพูลสตริง JVM มีสิทธิ์สำหรับการรวบรวมขยะหากไม่มีการอ้างอิงถึงสตริงเหล่านี้จากรูทโปรแกรมของคุณ ใช้กับ Java เวอร์ชันที่กล่าวถึงทั้งหมด หมายความว่าหากสตริงภายในของคุณอยู่นอกขอบเขตและไม่มีการอ้างอิงอื่นใดมันจะเป็นขยะที่รวบรวมจากสตริงพูล JVM

มีสิทธิ์ได้รับการรวบรวมขยะและอยู่ในฮีปพูลสตริง JVM ดูเหมือนจะเป็นสถานที่ที่เหมาะสมสำหรับสตริงทั้งหมดของคุณใช่ไหม ในทางทฤษฎีมันเป็นความจริง - สตริงที่ไม่ได้ใช้จะเป็นขยะที่รวบรวมจากพูลสตริงที่ใช้จะช่วยให้คุณสามารถบันทึกหน่วยความจำในกรณีที่คุณได้รับสตริงที่เท่ากันจากอินพุต ดูเหมือนจะเป็นกลยุทธ์การประหยัดหน่วยความจำที่สมบูรณ์แบบ? เกือบจะเป็นเช่นนั้น คุณต้องทราบวิธีการใช้งาน string pool ก่อนที่จะตัดสินใจใด ๆ

แหล่งที่มา


11

ดังคำตอบอื่น ๆ อธิบายหน่วยความจำใน Java แบ่งออกเป็นสองส่วน

1. สแต็ก:หนึ่งสแต็กถูกสร้างขึ้นต่อเธรดและเก็บสแต็กเฟรมซึ่งเก็บตัวแปรโลคัลอีกครั้งและหากตัวแปรเป็นชนิดการอ้างอิงตัวแปรนั้นจะอ้างถึงตำแหน่งหน่วยความจำในฮีปสำหรับอ็อบเจ็กต์จริง

2. กอง:วัตถุทุกชนิดจะถูกสร้างขึ้นในฮีปเท่านั้น

หน่วยความจำฮีปแบ่งออกเป็น 3 ส่วนอีกครั้ง

1. หนุ่มรุ่น:ร้านค้าวัตถุซึ่งมีชีวิตสั้นหนุ่มรุ่นตัวเองสามารถแบ่งออกเป็นสองประเภทEden อวกาศและรอดชีวิตอวกาศ

2. รุ่นเก่า:จัดเก็บวัตถุที่รอดชีวิตจากรอบการเก็บขยะจำนวนมากและยังคงถูกอ้างอิง

3. การสร้างแบบถาวร: จัดเก็บข้อมูลเมตาเกี่ยวกับโปรแกรมเช่นพูลค่าคงที่รันไทม์

สตริงคงพูลเป็นของพื้นที่การสร้างถาวรของหน่วยความจำฮีป

เราสามารถดูพูลค่าคงที่รันไทม์สำหรับโค้ดของเราใน bytecode โดยใช้javap -verbose class_nameซึ่งจะแสดงการอ้างอิงเมธอด (#Methodref) คลาสอ็อบเจกต์ (#Class) สตริงลิเทอรัล (#String)

รันไทม์คงที่พูล

คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในบทความของฉันJVM จัดการกับวิธีการโอเวอร์โหลดและการลบล้างภายในได้อย่างไร


โปรดเปิดเผยความสัมพันธ์ใด ๆและอย่าใช้ไซต์นี้เพื่อส่งเสริมไซต์ของคุณผ่านการโพสต์ ดูฉันจะเขียนคำตอบที่ดีได้อย่างไร? .

9

สำหรับคำตอบที่ยอดเยี่ยมที่รวมไว้ที่นี่แล้วฉันต้องการเพิ่มสิ่งที่ขาดหายไปในมุมมองของฉัน - ภาพประกอบ

ในขณะที่คุณ JVM แบ่งหน่วยความจำที่จัดสรรให้กับโปรแกรม Java ออกเป็นสองส่วน หนึ่งคือสแต็คและอีกคนหนึ่งเป็นกอง สแต็คถูกใช้เพื่อวัตถุประสงค์ในการดำเนินการและฮีปถูกใช้เพื่อการจัดเก็บ ในหน่วยความจำฮีปนั้น JVM จะจัดสรรหน่วยความจำบางส่วนที่มีไว้สำหรับตัวอักษรสตริงโดยเฉพาะ เป็นส่วนหนึ่งของหน่วยความจำกองนี้จะเรียกว่าสระว่ายน้ำคงที่สตริง

ตัวอย่างเช่นหากคุณเริ่มต้นวัตถุต่อไปนี้:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

สตริงลิเทอรัลs1และs2จะไปที่พูลค่าคงที่สตริงอ็อบเจกต์ obj1, obj2, obj3 ไปยังฮีพ ทั้งหมดนี้จะอ้างอิงจาก Stack

นอกจากนี้โปรดทราบว่า "abc" จะปรากฏในฮีปและในพูลค่าคงที่ของสตริง ทำไมString s1 = "abc"และString obj1 = new String("abc")จะถูกสร้างขึ้นด้วยวิธีนี้? เป็นเพราะString obj1 = new String("abc")สร้างอินสแตนซ์ใหม่ที่แตกต่างกันอย่างชัดเจนของออบเจ็กต์ String และString s1 = "abc"อาจใช้อินสแตนซ์จากพูลค่าคงที่สตริงซ้ำได้หากมี สำหรับคำอธิบายที่ละเอียดยิ่งขึ้น: https://stackoverflow.com/a/3298542/2811258

ใส่คำอธิบายภาพที่นี่


ในแผนภาพที่ระบุตัวอักษร "def" และ "456" จะอยู่ที่ไหน และจะอ้างอิงอย่างไร?
Satyendra

ขอบคุณสำหรับความคิดเห็นของคุณ @Satyendra ฉันได้อัปเดตภาพประกอบและคำตอบแล้ว
Johnny

@Stas ทำไม String object "abc" ถูกสร้างขึ้น.. มันควรจะใช้ reference obj1 เพื่อชี้ลิเทอรัลใช่ไหม

เป็นเพราะ String obj1 = new String ("abc") สร้างอินสแตนซ์ใหม่ที่แตกต่างกันอย่างชัดเจนของออบเจ็กต์ String และ String s1 = "abc" อาจนำอินสแตนซ์จากพูลค่าคงที่ของสตริงกลับมาใช้ใหม่หากมี สำหรับคำอธิบายที่ละเอียดยิ่งขึ้น: stackoverflow.com/a/3298542/2811258
Johnny
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.