การจัดการหน่วยความจำใน Java
⚡ สรุปอย่างชาญฉลาด
การจัดการหน่วยความจำใน Java อธิบายว่า JVM แบ่งหน่วยความจำขณะรันไทม์อย่างไร โดยแบ่งเป็น Stack, Heap, Codeและส่วนต่างๆ ของหน่วยความจำแบบคงที่ วิธีที่การอ้างอิงวัตถุไหลเวียนระหว่างการเรียกใช้เมธอด และวิธีที่ตัวเก็บขยะ (Garbage Collector) เรียกคืนวัตถุที่ไม่สามารถเข้าถึงได้ เพื่อให้แอปพลิเคชันทำงานได้อย่างเสถียรและปราศจากปัญหาหน่วยความจำรั่วไหล

หน่วยความจำแบบ Stack คืออะไรใน Java?
หน่วยความจำแบบเรียงซ้อนใน Java Stack คือส่วนของหน่วยความจำ JVM ที่เก็บเฟรมของเมธอด ตัวแปรโลคอล และตัวแปรอ้างอิงสำหรับแต่ละเธรด การเข้าถึง Stack จะทำในลำดับ Last-In-First-Out เสมอ ดังนั้นเมธอดที่ถูกเรียกใช้ล่าสุดจะอยู่ด้านบนสุด และตัวแปรโลคอลของเมธอดนั้นจะถูกผลักและดึงออกจาก Stack พร้อมกับเฟรม
แต่ละเส้นด้ายใน Java เครื่องเสมือน (Virtual Machine) จะมี Stack เป็นของตัวเอง ซึ่งทำให้การเรียกใช้เมธอดแยกออกจากกันและปลอดภัยต่อการทำงานแบบมัลติเธรดโดยธรรมชาติ ตัวแปรโลคอลพื้นฐาน เช่น int, boolean และ double จะถูกเก็บไว้ภายในเฟรมโดยตรง ในขณะที่การอ้างอิงวัตถุที่เก็บไว้ใน Stack จะชี้ไปยังวัตถุที่จัดสรรไว้ใน Heap
หน่วยความจำฮีปคืออะไรใน Java?
หน่วยความจำฮีป (Heap memory) คือพื้นที่ส่วนกลางของ JVM ที่ใช้เก็บข้อมูลทุกอย่าง Java อ็อบเจ็กต์และอาร์เรย์ที่สร้างขึ้นด้วยตัวดำเนินการ `new` รวมถึงตัวแปรอ้างอิงใดๆ ที่เป็นของอ็อบเจ็กต์เหล่านั้นในฐานะฟิลด์อินสแตนซ์ ต่างจาก Stack ตรงที่ Heap นั้นใช้ร่วมกันในทุกเธรด ซึ่งเป็นเหตุผลว่าทำไมการเข้าถึงอ็อบเจ็กต์จึงมักต้องใช้การซิงโครไนซ์
ฮีป (Heap) คือพื้นที่ที่ตัวเก็บขยะ (Garbage Collector) จัดการ JVM รุ่นใหม่ๆ ที่ใช้ HotSpot จะแบ่งฮีปออกเป็น Young Generation สำหรับวัตถุที่มีอายุสั้น และ Old Generation สำหรับวัตถุที่มีอายุยาวนาน โดยมีเมตาเดตาของคลาสจัดเก็บไว้ในพื้นที่เฉพาะที่เรียกว่า Metaspace
การจัดสรรหน่วยความจำใน Java
การจัดสรรหน่วยความจำใน Java Heap คือกระบวนการที่ JVM จัดสรรพื้นที่หน่วยความจำเสมือนสำหรับตัวแปรและอินสแตนซ์ของคลาสและโครงสร้างระหว่างการทำงานของโปรแกรม หน่วยความจำไม่ได้ถูกจัดสรรให้กับวัตถุเมื่อมีการประกาศ แต่จะสร้างเพียงการอ้างอิงเท่านั้น การจัดสรรวัตถุจริงเกิดขึ้นผ่านตัวดำเนินการ `new` ดังนั้นทุกวัตถุจึงอยู่ใน Heap
การขอ Java การจัดสรรหน่วยความจำแบ่งออกเป็นส่วนต่างๆ ดังต่อไปนี้:
- กอง
- กอง
- Code
- คงที่
การแบ่งหน่วยความจำนี้จำเป็นสำหรับการจัดการระบบรันไทม์อย่างมีประสิทธิภาพ
- การขอ Code ส่วนนี้ประกอบด้วยข้อมูลที่คุณรวบรวมไว้ รหัสไบต์.
- การขอ กอง ร้านค้าแผนก วิธีการ ตัวแปรท้องถิ่น และตัวแปรอ้างอิง.
- การขอ กอง ส่วนประกอบด้วย วัตถุ และอาจมีตัวแปรอ้างอิงที่จัดเก็บไว้ในรูปแบบฟิลด์ของอินสแตนซ์ด้วย
- การขอ คงที่ ส่วนนี้ถือ ข้อมูลคงที่และเมธอดคงที่ ใช้ร่วมกันในทุกอินสแตนซ์
ความแตกต่างระหว่างตัวแปรท้องถิ่นและอินสแตนซ์
การเข้าใจว่าตัวแปรแต่ละประเภทอยู่ที่ใดใน JVM จะช่วยอธิบายพฤติกรรมของ Stack และ Heap ได้ ตัวแปรอินสแตนซ์ มีการประกาศ ภายในคลาสแต่ภายนอกเมธอดใดๆและมันยังคงดำรงอยู่บนกองขยะนั้นในฐานะส่วนหนึ่งของวัตถุนั้น
class Student{ int num; // num is instance variable public void showData{}
A ตัวแปรท้องถิ่น มีการประกาศ ภายในเมธอด รวมถึงอาร์กิวเมนต์ของเมธอดด้วยและดำรงอยู่บน Stack ภายในเฟรมที่ใช้งานอยู่
public void sum(int a){ int x = a + 3; // a, x are local variables }
ความแตกต่างระหว่างสแต็คและฮีป
Stack และ Heap แก้ปัญหาที่แตกต่างกันภายใน JVM Stack ช่วยให้แต่ละเธรดจัดสรรพื้นที่ได้อย่างรวดเร็วและแน่นอนสำหรับข้อมูลที่มีอายุสั้นซึ่งผูกติดกับขอบเขตของเมธอด ในขณะที่ Heap ให้พื้นที่ส่วนกลางสำหรับวัตถุที่มีอายุยาวนานซึ่งเธรดใด ๆ ก็สามารถอ้างอิงได้ วิดีโอสั้น ๆ ด้านล่างนี้สรุปความแตกต่างก่อนที่จะมีการอธิบายรายละเอียดต่อไป
คลิก Good Farm Animal Welfare Awards หากไม่สามารถเข้าถึงวิดีโอได้
เพื่อให้เข้าใจการทำงานร่วมกันของ Stack และ Heap ลองพิจารณาเมธอด main ที่เรียกใช้เมธอด m1
public void m1{ int x = 20; }
ใน JVM Stack จะมีการสร้างเฟรมสำหรับเมธอด m1
ตัวแปร x ใน m1 ถูกสร้างขึ้นในเฟรมสำหรับ m1 บน Stack ดังแสดงในภาพด้านล่าง
จากนั้นเมธอด m1 จะเรียกเมธอด m2 ในสแต็ก จะมีการสร้างเฟรมใหม่สำหรับ m2 ทับเฟรมสำหรับ m1
ตัวแปรโลคอล b และ c ถูกสร้างขึ้นภายในเฟรมสำหรับ m2 บน Stack ด้วยเช่นกัน
public void m2(int b){ boolean c; }
ถัดไป m2 จะเรียกเมธอด m3 อีกครั้ง โดยจะมีการสร้างเฟรมสำหรับ m3 ไว้ที่ด้านบนสุดของ Stack ดังแสดงในภาพด้านล่าง
สมมติว่าเมธอด m3 สร้างอ็อบเจ็กต์สำหรับคลาส Account ซึ่งมีสองส่วน ตัวแปรอินสแตนซ์ int p และ int q
class Account { int p; int q; }
นี่คือโค้ดสำหรับเมธอด m3
public void m3(){ Account ref = new Account(); // more code }
คำสั่ง `new Account()` สร้างอ็อบเจ็กต์ของ `Account` บนฮีป (Heap)
ตัวแปรอ้างอิง ref ถูกสร้างขึ้นบน Stack ภายในเฟรมสำหรับ m3
ตัวดำเนินการกำหนดค่าจะทำให้ตัวแปรอ้างอิงชี้ไปยังอ็อบเจ็กต์บนฮีป
เมื่อเมธอดทำงานเสร็จสิ้น การควบคุมจะกลับไปยังเมธอดที่เรียก ซึ่งในกรณีนี้คือเมธอด m2
เฟรมสำหรับเมธอด m3 ถูกล้างออกจากสแต็กแล้ว
เนื่องจากตัวแปรอ้างอิงไม่ได้ชี้ไปยังอ็อบเจ็กต์บนฮีปอีกต่อไป อ็อบเจ็กต์นั้นจึงมีสิทธิ์ได้รับการเก็บกวาดโดยระบบจัดการหน่วยความจำ (garbage collection)
เมื่อเมธอด m2 ทำงานเสร็จสิ้น เมธอดนั้นจะถูกดึงออกจากสแต็ก และตัวแปรทั้งหมดจะไม่สามารถใช้งานได้อีกต่อไป เช่นเดียวกับเมธอด m1 และในที่สุดการควบคุมจะกลับไปยังเมธอดหลัก
แล้วถ้าอ็อบเจ็กต์นั้นเก็บการอ้างอิงอื่นไว้เป็นตัวแปรอินสแตนซ์ล่ะ?
public static void main(String args[]) { A parent = new A(); // more code } class A { B child = new B(); int e; } class B { int c; int d; }
ในกรณีนี้ ตัวแปรอ้างอิง child จะอยู่บนฮีปเป็นส่วนหนึ่งของอ็อบเจ็กต์ A และชี้ไปยังอ็อบเจ็กต์ B ของตัวเอง ดังแสดงในภาพด้านล่าง
Garbage Collection อยู่ในอะไร Java?
เก็บขยะใน Java Garbage Collector คือกระบวนการที่ JVM จัดการหน่วยความจำโดยอัตโนมัติ Garbage Collector จะค้นหาวัตถุที่ไม่สามารถเข้าถึงได้อีกต่อไปจากการอ้างอิงใดๆ และเรียกคืนหน่วยความจำนั้น การจัดสรรหน่วยความจำแบบไดนามิกเกิดขึ้นผ่านตัวดำเนินการ `new` และหน่วยความจำนั้นจะยังคงถูกจัดสรรไว้จนกว่าโปรแกรมจะไม่ถือการอ้างอิงถึงวัตถุนั้นอีกต่อไป
เมื่อไม่มีการอ้างอิงถึงวัตถุนั้นอีกต่อไป วัตถุนั้นจะถือว่าไม่จำเป็นอีกต่อไป และสามารถเรียกคืนหน่วยความจำที่วัตถุนั้นครอบครองได้ ไม่จำเป็นต้องทำลายวัตถุอย่างชัดเจนเพราะ Java จัดการการคืนหน่วยความจำโดยอัตโนมัติผ่านตัวเก็บขยะ (Garbage Collector)
เทคนิคเบื้องหลังเรื่องนี้เรียกว่า... เก็บขยะโปรแกรมที่ไม่สามารถปล่อยหน่วยความจำได้ในที่สุดจะหยุดทำงานเมื่อไม่มีหน่วยความจำเหลือให้จัดสรรอีกต่อไป โปรแกรมประเภทนี้เรียกว่ามี หน่วยความจำรั่ว. เก็บขยะใน Java ทำงานโดยอัตโนมัติ ตลอดอายุการใช้งานของโครงการ ซึ่งจะช่วยลดภาระในการจัดสรรทรัพยากรด้วยตนเองและลดความเสี่ยงของการรั่วไหล
ในภาษา C ในทางตรงกันข้าม โปรแกรมเมอร์มีหน้าที่รับผิดชอบในการปล่อยหน่วยความจำที่จัดสรรแบบไดนามิกผ่านฟังก์ชัน free() นี่คือจุดที่ Java การจัดการหน่วยความจำถือเป็นข้อได้เปรียบที่สำคัญ
JVM รุ่นใหม่ๆ ของ HotSpot มาพร้อมกับ Garbage Collector หลายตัวที่ปรับแต่งมาสำหรับภาระงานที่แตกต่างกัน Garbage Collector G1 ที่เป็นค่าเริ่มต้นนั้นมุ่งเน้นที่ความสมดุลระหว่างปริมาณงานและเวลาหยุดชั่วคราวบนฮีปขนาดหลายกิกะไบต์ ในขณะที่ ZGC และ Shenandoah มุ่งเป้าไปที่เวลาหยุดชั่วคราวต่ำกว่ามิลลิวินาทีบนฮีปขนาดใหญ่มาก ซึ่งทำให้พวกมันมีประสิทธิภาพที่...tracมีประสิทธิภาพสำหรับบริการที่ไวต่อความหน่วงแฝง การเลือกตัวเก็บรวบรวมข้อมูลที่เหมาะสมและการปรับแต่งขนาดของ Young Generation, Old Generation และ Metaspace เป็นทักษะหลักสำหรับ Java งานแสดง.
หมายเหตุ วัตถุทั้งหมดถูกสร้างขึ้นในส่วนของหน่วยความจำที่เรียกว่า Heap ซึ่งเป็นบริเวณที่ Garbage Collector จัดการ
ตัวอย่าง: เรียนรู้กลไกการเก็บขยะใน Java
คู่มือนี้แสดงให้เห็นว่าเมื่อใดที่การอ้างอิงต่างๆ จะมีสิทธิ์ได้รับการลบทิ้งโดยระบบ (Garbage Collection) ภายในโปรแกรมอย่างง่าย
ขั้นตอน 1) คัดลอกโค้ดต่อไปนี้ลงในโปรแกรมแก้ไข
class Student{ int a; int b; public void setData(int c,int d){ a=c; b=d; } public void showData(){ System.out.println("Value of a = "+a); System.out.println("Value of b = "+b); } public static void main(String args[]){ Student s1 = new Student(); Student s2 = new Student(); s1.setData(1,2); s2.setData(3,4); s1.showData(); s2.showData(); //Student s3; //s3=s2; //s3.showData(); //s2=null; //s3.showData(); //s3=null; //s3.showData(); } }
ขั้นตอน 2) บันทึก คอมไพล์ และรันโค้ด ดังแสดงในแผนภาพ จะมีการสร้างอ็อบเจ็กต์สองตัวและตัวแปรอ้างอิงสองตัว
ขั้นตอน 3) ลบเครื่องหมายคอมเมนต์ออกจากบรรทัดที่ 20, 21 และ 22 บันทึก คอมไพล์ และรันโค้ด
ขั้นตอน 4) ดังแสดงในแผนภาพด้านล่าง ตัวแปรอ้างอิงสองตัวชี้ไปยังวัตถุเดียวกันแล้ว
ขั้นตอน 5) ลบเครื่องหมายคอมเมนต์ออกจากบรรทัดที่ 23 และ 24 บันทึก คอมไพล์ และรันโค้ด
ขั้นตอน 6) ดังแสดงในภาพด้านล่าง s2 จะกลายเป็นค่าว่าง แต่ s3 ยังคงชี้ไปยังวัตถุนั้นอยู่ ดังนั้นวัตถุนั้นจึงยังไม่พร้อมสำหรับการเก็บกวาดขยะ (Garbage Collection)
ขั้นตอน 7) ลบเครื่องหมายคอมเมนต์ออกจากบรรทัดที่ 25 และ 26 บันทึก คอมไพล์ และรันโค้ด
ขั้นตอน 8) ณ จุดนี้ ไม่มีข้อมูลอ้างอิงใดชี้ไปยังวัตถุนั้นอีกแล้ว ดังนั้นวัตถุนั้นจึงมีสิทธิ์ได้รับการเก็บกวาดโดยระบบจัดการขยะ (Garbage Collection) ระบบจัดการขยะจะลบวัตถุนั้นออกจากหน่วยความจำ และไม่มีวิธีใดที่จะเรียกคืนวัตถุนั้นได้อีก
วิธีการลบวัตถุใน Java?
Java ไม่มีตัวดำเนินการลบด้วยตนเอง ดังนั้นวิธีการมาตรฐานคือการลบการอ้างอิงทั้งหมดไปยังวัตถุนั้น เพื่อให้ตัวเก็บขยะสามารถเรียกคืนวัตถุนั้นได้
1) เพื่อให้วัตถุพร้อมสำหรับการเก็บขยะ (Garbage Collection) ให้กำหนดค่าตัวแปรอ้างอิงทุกตัวที่ชี้ไปยังวัตถุนั้นเป็นค่าว่าง (null)
2) ประเภทข้อมูลพื้นฐานไม่ใช่วัตถุ ดังนั้นจึงไม่สามารถกำหนดค่าเป็น null ได้ พื้นที่จัดเก็บจะถูกเรียกคืนโดยอัตโนมัติเมื่อเฟรมสแต็กโดยรอบถูกดึงออก


















