หน่วยความจำ Stack และ Heap ใน Java


99

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

สมมติว่าฉันมีชั้นเรียน

class A {
       int a ;
       String b;
       //getters and setters
}
  1. ดั้งเดิมaในชั้นเรียนAจะถูกเก็บไว้ที่ไหน

  2. เหตุใดหน่วยความจำฮีปจึงมีอยู่ทั้งหมด ทำไมเราไม่เก็บทุกอย่างไว้ในกองซ้อน?

  3. เมื่อวัตถุได้รับการรวบรวมขยะสแต็กที่เกี่ยวข้องกับวัตถุที่ถูกทำลายจะถูกจัดเก็บหรือไม่?


1
stackoverflow.com/questions/1056444/is-it-on-the-stack-or-heapดูเหมือนจะตอบคำถามของคุณ
S.Lott

@ S.Lott ยกเว้นว่าอันนี้เป็นเรื่องเกี่ยวกับ Java ไม่ใช่ C.
PéterTörök

@ PéterTörök: เห็นด้วย ในขณะที่ตัวอย่างโค้ดคือ Java ไม่มีแท็กที่ระบุว่าเป็นเพียง Java และหลักการทั่วไปควรนำไปใช้กับ Java เช่นเดียวกับ C. นอกจากนี้ยังมีคำตอบมากมายสำหรับคำถามนี้เกี่ยวกับ Stack Overflow
S.Lott

9
@SteveHaigh: ในเว็บไซต์นี้ทุกคนมีความกังวลมากเกินไปเกี่ยวกับว่ามีบางสิ่งบางอย่างอยู่ที่นี่หรือไม่ ... ฉันสงสัยว่าเว็บไซต์นี้มีความคิดอย่างไรกับการที่ทุกคนกังวลใจเกี่ยวกับคำถามที่อยู่ที่นี่หรือไม่
Sam Goldberg

คำตอบ:


106

ความแตกต่างพื้นฐานระหว่างสแต็คและฮีปคือวงจรชีวิตของค่า

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

ตอนนี้ Java เก็บเฉพาะดั้งเดิมบนสแต็ก สิ่งนี้ทำให้สแต็กเล็กและช่วยให้แต่ละสแต็กเฟรมเล็กทำให้สามารถโทรซ้อนได้มากขึ้น
วัตถุถูกสร้างขึ้นบนฮีปและจะมีการอ้างอิงเท่านั้น (ซึ่งจะเป็นแบบดั้งเดิม) เท่านั้นที่จะถูกส่งผ่านไปบนสแต็ก

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


2
"และการอ้างอิงเท่านั้น (ซึ่งจะเป็นแบบดั้งเดิม)" ทำไมจึงกล่าวว่าการอ้างอิงนั้นเป็นแบบดั้งเดิม คุณช่วยอธิบายได้ไหม
Geek

5
@Geek: เพราะความหมายทั่วไปของชนิดข้อมูลดั้งเดิมใช้: "ชนิดข้อมูลให้โดยภาษาการเขียนโปรแกรมเป็นกลุ่มอาคารพื้นฐาน" นอกจากนี้คุณยังอาจสังเกตเห็นว่าลำดับที่มีการระบุไว้ในหมู่ตัวอย่างที่ยอมรับต่อไปลงในบทความ
back2dos

4
@Geek: ในแง่ของข้อมูลคุณสามารถดูชนิดข้อมูลดั้งเดิมใด ๆ รวมถึงการอ้างอิงเป็นตัวเลข แม้แต่chars คือตัวเลขและสามารถใช้แทนกันได้เช่นกัน การอ้างอิงเป็นเพียงตัวเลขที่อ้างถึงที่อยู่หน่วยความจำทั้ง 32 หรือ 64 บิตยาว (แม้ว่าพวกเขาจะไม่สามารถใช้งานได้เช่น - เว้นแต่คุณกำลังยุ่งกับsun.misc.Unsafe)
Sune Rasmussen

3
คำศัพท์ของคำตอบนี้ไม่ถูกต้อง ตามข้อกำหนดภาษา Java การอ้างอิงไม่ใช่แบบดั้งเดิม ส่วนสำคัญของสิ่งที่คำตอบบอกว่าถูกต้อง (ในขณะที่คุณสามารถทำให้ข้อโต้แย้งที่อ้างอิง "ในความรู้สึก" ดั้งเดิมโดยโดยที่ JLS กำหนดคำศัพท์สำหรับ Java และมันบอกว่าชนิดดั้งเดิม. boolean, byte, short, char, int, long, floatและdouble.)
สตีเฟ่นซี

4
ดังที่ฉันพบในบทความนี้ Java อาจเก็บวัตถุไว้ในสแต็ก (หรือแม้แต่ในการลงทะเบียนสำหรับวัตถุขนาดเล็กอายุสั้น) JVM สามารถทำได้ค่อนข้างน้อยภายใต้ฝาครอบ มันไม่ถูกต้องอย่างแน่นอนที่จะพูดว่า "ตอนนี้ Java เก็บเฉพาะดั้งเดิมบนสแต็ก"

49

ฟิลด์ดั้งเดิมถูกเก็บไว้ที่ไหน

สาขาดั้งเดิมจะถูกเก็บไว้เป็นส่วนหนึ่งของวัตถุที่ถูกสร้างที่ไหนสักแห่ง วิธีที่ง่ายที่สุดที่จะคิดว่านี่คือที่ไหน - คือกอง อย่างไรก็ตามนี่ไม่ใช่กรณีเสมอไป ตามที่อธิบายในทฤษฎีและการปฏิบัติของ Java: Urban performance legends, revisited :

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

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

กระดาษสรุปด้วย:

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

ดังนั้นหากคุณมีรหัสที่ดูเหมือนว่า:

void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}

โดยที่...ไม่อนุญาตให้quxออกจากขอบเขตนั้นqux อาจถูกจัดสรรในสแต็กแทน นี่เป็นความจริงที่ชนะสำหรับ VM เพราะมันหมายความว่าไม่จำเป็นต้องมีการรวบรวมขยะ - มันจะหายไปเมื่อมันออกจากขอบเขต

เพิ่มเติมเกี่ยวกับการวิเคราะห์การหลบหนีที่ Wikipedia สำหรับผู้ที่ต้องการเจาะลึกลงไปในเอกสารEscape Analysis for Javaจาก IBM สำหรับผู้ที่มาจากโลก C # คุณอาจพบรายละเอียดการดำเนินการและความจริงเกี่ยวกับประเภทค่าโดย Eric Lippert อ่านดี (พวกมันมีประโยชน์สำหรับประเภท Java เช่นเดียวกับแนวคิดและแง่มุมต่าง ๆ ที่เหมือนกันหรือคล้ายกัน) . ทำไมหนังสือ. Net พูดถึงการจัดสรรหน่วยความจำ stack vs heap ยังไปในนี้

บนกองของกองและกอง

บนกอง

เหตุใดจึงมีกองหรือกองเลย? สำหรับสิ่งต่าง ๆ ที่ออกจากขอบเขต พิจารณารหัส:

void foo(String arg) {
    bar(arg);
    ...
}

void bar(String arg) {
    qux(arg);
    ...
}

void qux(String arg) {
    ...
}

พารามิเตอร์เป็นส่วนหนึ่งของสแต็กด้วย ในสถานการณ์ที่คุณไม่มีฮีปคุณจะผ่านชุดเต็มของค่าในสแต็ก นี่เป็นสิ่งที่ดี"foo"และสตริงเล็ก ๆ ... แต่จะเกิดอะไรขึ้นถ้ามีคนใส่ไฟล์ XML ขนาดใหญ่ไว้ในสตริงนั้น การโทรแต่ละครั้งจะคัดลอกสตริงขนาดใหญ่ทั้งหมดลงในสแต็ก - และนั่นจะค่อนข้างสิ้นเปลือง

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

บนสแต็ค

คุณไม่ต้องการสแต็ค หนึ่งอาจสมมุติเขียนภาษาที่ไม่ได้ใช้สแต็ค (ของความลึกโดยพลการ) พื้นฐานเก่าที่ฉันได้เรียนรู้ในวัยเยาว์ของฉันทำเช่นนั้นเราทำได้เพียง 8 ระดับการgosubโทรเท่านั้นและตัวแปรทั้งหมดเป็นโลก - ไม่มีสแต็ค

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

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

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

เมื่อสิ่งที่เป็นขยะที่เก็บรวบรวม

เมื่อสิ่งที่เก็บขยะมันหายไป แต่มันเป็นเพียงขยะที่เก็บรวบรวมเพราะมันถูกลืมไปแล้ว - ไม่มีการอ้างอิงถึงวัตถุในโปรแกรมที่สามารถเข้าถึงได้จากสถานะปัจจุบันของโปรแกรม

ฉันจะชี้ให้เห็นว่านี่เป็นการรวบรวมขยะขนาดใหญ่มาก ๆ (แม้ใน Java - คุณสามารถปรับแต่งตัวรวบรวมขยะโดยใช้ค่าสถานะต่าง ๆ ( docs ) สิ่งเหล่านี้ทำงานแตกต่างกันและความแตกต่างของวิธีการที่แต่ละคนทำสิ่งต่าง ๆ ลึกเกินไปสำหรับคำตอบนี้คุณอาจต้องการอ่านJava Garbage Collection Basicsเพื่อให้ได้แนวคิดที่ดีขึ้นเกี่ยวกับวิธีการทำงานบางอย่าง

ที่กล่าวว่าหากมีการจัดสรรบางสิ่งบางอย่างบนสแต็กจะไม่ถูกรวบรวมขยะเป็นส่วนหนึ่งของSystem.gc()- มันจะถูกจัดสรรคืนเมื่อสแต็กเฟรมปรากฏขึ้น หากมีบางสิ่งอยู่ในฮีปและถูกอ้างอิงจากบางสิ่งบนสแต็กมันจะไม่ถูกรวบรวมขยะในเวลานั้น

เหตุใดเรื่องนี้

ส่วนใหญ่เป็นประเพณีของมัน หนังสือเรียนที่เขียนและเรียบเรียงคอมไพเลอร์และเอกสารประกอบบิตต่าง ๆ ทำให้เป็นเรื่องใหญ่เกี่ยวกับ heap และ stack

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

วัตถุอยู่ที่ไหนสักแห่งและอยู่ในสถานที่ที่สามารถเข้าถึงได้อย่างถูกต้องและรวดเร็วตามระยะเวลาที่เหมาะสม หากอยู่ในกองหรือกอง - มันไม่สำคัญ


7
  1. ในฮีปซึ่งเป็นส่วนหนึ่งของวัตถุซึ่งอ้างอิงโดยตัวชี้ในสแต็ก กล่าวคือ a และ b จะถูกเก็บไว้ติดกัน
  2. เพราะถ้าหากหน่วยความจำทั้งหมดเป็นหน่วยความจำสแต็คก็จะไม่มีประสิทธิภาพอีกต่อไป เป็นการดีที่จะมีพื้นที่เข้าถึงขนาดเล็กที่รวดเร็วซึ่งเราเริ่มต้นและมีรายการอ้างอิงนั้นในพื้นที่หน่วยความจำขนาดใหญ่ที่เหลืออยู่ อย่างไรก็ตามนี่คือ overkill เมื่อวัตถุเป็นเพียงหนึ่งดั้งเดิมซึ่งจะใช้เวลาประมาณจำนวนเดียวกันของพื้นที่บนสแต็คเป็นตัวชี้ไป
  3. ใช่.

1
ฉันจะเพิ่มไปยังจุดของคุณ # 2 ว่าถ้าคุณเก็บวัตถุบนกองซ้อน (นึกภาพวัตถุพจนานุกรมที่มีหลายร้อยหลายพันรายการ) จากนั้นเพื่อส่งผ่านไปยังหรือส่งคืนจากฟังก์ชั่นที่คุณต้องการคัดลอกวัตถุทุก เวลา. โดยการใช้ตัวชี้หรือการอ้างอิงถึงวัตถุในฮีปเราจะส่งการอ้างอิง (เล็ก) เท่านั้น
Scott Whitlock

1
ฉันคิดว่า 3 จะเป็น 'ไม่' เพราะหากวัตถุถูกเก็บขยะแล้วไม่มีการอ้างอิงในสแต็กที่ชี้ไป
Luciano

@Luciano - ฉันเห็นจุดของคุณ ฉันอ่านคำถาม 3 แตกต่างกัน "ในเวลาเดียวกัน" หรือ "ตามเวลานั้น" เป็นนัย :: ยัก ::
สาธารณรัฐประชาธิปไตยประชาชนลาว

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

  2. หน่วยความจำสแต็กต้องถูกจัดสรรและยกเลิกการจัดสรรในลำดับสุดท้ายก่อน หน่วยความจำฮีปสามารถจัดสรรและจัดสรรคืนในลำดับใดก็ได้

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


3

หน่วยความจำสแต็กใช้เพื่อจัดเก็บตัวแปรโลคัลและการเรียกฟังก์ชัน

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

ไหนจะดั้งเดิมaในclass Aถูกเก็บไว้?

ในกรณีนี้ดั้งเดิมมีความเกี่ยวข้องกับวัตถุคลาส A ดังนั้นมันจึงสร้างในหน่วยความจำฮีป

เหตุใดหน่วยความจำฮีปจึงมีอยู่ทั้งหมด ทำไมเราไม่เก็บทุกอย่างไว้ในกองซ้อน?

  • ตัวแปรที่สร้างบนสแต็กจะไม่อยู่ในขอบเขตและถูกทำลายโดยอัตโนมัติ
  • กองซ้อนเร็วกว่ามากในการจัดสรรเมื่อเปรียบเทียบกับตัวแปรในฮีป
  • ตัวแปรบนฮีปต้องถูกทำลายโดย Garbage Collector
  • การจัดสรรช้ากว่าเมื่อเปรียบเทียบกับตัวแปรในสแต็ก
  • คุณจะใช้สแต็กถ้าคุณรู้ว่าต้องใช้ข้อมูลเท่าไหร่ในการจัดสรรก่อนรวบรวมเวลาและไม่ใหญ่เกินไป (ตัวแปรโลคอลดั้งเดิมเก็บในสแต็ก)
  • คุณจะใช้ฮีปถ้าคุณไม่รู้ว่าคุณต้องใช้ข้อมูลเท่าไรในรันไทม์หรือถ้าคุณต้องการจัดสรรข้อมูลจำนวนมาก

เมื่อวัตถุได้รับการรวบรวมขยะสแต็กที่เกี่ยวข้องกับวัตถุที่ถูกทำลายจะถูกจัดเก็บหรือไม่?

Garbage Collector ทำงานภายใต้ขอบเขตของหน่วยความจำฮีปจึงทำลายวัตถุที่ไม่มีห่วงโซ่อ้างอิงตั้งแต่ถึงรูต


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.