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

ต่างจาก C, C++ ใน Java ที่เรามี Garbage Collector เพื่อช่วยเราไม่ให้ทำความสะอาดโต๊ะหลังจากที่เราทานอาหารเสร็จ ในส่วนนี้จะอธิบายโดยย่อเกี่ยวกับการจัดการหน่วยความจำใน Java

การจัดการหน่วยความจำมีสองส่วน

  1. หน่วยความจำถูกจัดสรรและอ้างอิงอย่างไร
  2. หน่วยความจำถูกล้างและพร้อมสำหรับการจัดสรรอีกครั้งอย่างไร

มาดูอันแรกกัน..

โดยรวมแล้ว เราสามารถพูดได้ว่าหน่วยความจำแบ่งออกเป็นสามส่วน ได้แก่ Stack, Heap และ non-Heap มาดูกันตามลำดับ

ซ้อนกัน

  • ตัวแปรบนสแต็กเก็บการอ้างอิงถึงออบเจ็กต์บนฮีปและดั้งเดิม
  • มีหนึ่งสแต็กเดียวต่อเธรด
  • มีขอบเขตสำหรับตัวแปร
  • ขอบเขตวิธีการถูกผลักและเปิดขึ้นมา
  • สามารถกำหนดขนาดได้ผ่านการกำหนดค่าเมื่อเริ่มต้น

กอง

  • หนึ่งรายการต่อกระบวนการ JVM — แชร์ระหว่างเธรด
  • สามารถเก็บการอ้างอิงไปยังวัตถุอื่น ๆ ในฮีปได้
  • แบ่งออกเป็นส่วนๆ เพื่อง่ายต่อการล้าง และจัดสรรใหม่ (เราจะดูภายหลัง)
  • สามารถกำหนดขนาดได้ผ่านการกำหนดค่าเมื่อเริ่มต้น

ไม่ใช่ฮีป

  • หน่วยความจำที่จำเป็นสำหรับการประมวลผลภายในของ JVM
  • พูลคงที่, ตัวแปรคงที่
  • ข้อมูลภาคสนามและวิธีการ
  • รหัสสำหรับวิธีการและตัวสร้าง
  • คลาสโหลดเดอร์
  • PermGen (ฮีป/ไม่ใช่ฮีป) กับ Metaspace (Java 8)
  • อาจหรืออาจไม่ได้เป็นส่วนหนึ่งของ Heap - การใช้งานจะแตกต่างกันไป

ประเภทการอ้างอิง

ตอนนี้คุณอาจสงสัยว่าเหตุใดจึงมีเส้นประจากสแต็กหนึ่งไปอีกฮีป มาดูกันว่าตัวแปรใน Stack/Heap อ้างถึงอ็อบเจ็กต์บน Heap อย่างไร สิ่งเหล่านี้เรียกว่าการอ้างอิงและมีสี่รายการ

  • การอ้างอิงที่รัดกุม — การอ้างอิงปกติที่เราใช้ใน Java
A a = new A();
  • การอ้างอิงที่อ่อนแอ — การอ้างอิงถึงออบเจ็กต์ที่เสี่ยงต่อการรวบรวมขยะครั้งต่อไป (หากไม่ได้อ้างอิงอย่างเข้มงวด)
contextWeakReference = new WeakReference<Context>(context);
//Later on.....
Context context = contextWeakReference.get();
if(context != null){
    //Context is not Gc'ed yet
}else{ 
    //Context is Gc'ed already
}
  • Soft Reference — สิ่งนี้คล้ายกับ WeakReference เพียงแต่ JVM จะ GC สิ่งนี้เฉพาะเมื่อมีหน่วยความจำขัดข้องเท่านั้น และแน่ใจอย่างแน่นอนว่าการอ้างอิงแบบซอฟต์ทั้งหมดนั้นเป็นขยะที่รวบรวมก่อนที่ JVM จะส่ง OOM ใด ๆ
contextSoftReference = new SoftReference<Context>(context);
//Later on.....
Context context = contextSoftReference.get();
if(context != null){
    //Context is not Gc'ed yet
}else{ 
    //Context is Gc'ed already
}
  • การอ้างอิงแบบหลอก — ใช้สำหรับการล้างข้อมูลก่อนการชันสูตรพลิกศพ และแนะนำให้ใช้มากกว่า finalize() การสรุปผลเป็นสิ่งที่คาดเดาไม่ได้ ไม่สามารถกำหนดได้ และทำให้แอปพลิเคชันช้าลง ในตัวอย่างต่อไปนี้ เราต้องสำรวจ ReferenceQueue สำหรับการอ้างอิงอย่างต่อเนื่อง จากนั้นทำการล้างข้อมูล
public class PhantomReferenceTest {
    public static void main(String… args){
        ReferenceQueue rq = new ReferenceQueue();
        A a = new A();
        a.s ="hello";
        Reference r = new PhantomReference(a,rq);
        a =null;
        System.gc();
        Reference ref = (Reference) rq.poll();
        while(ref != null){
            System.out.println(ref.get());
        }
    }
}
class A{
    String s;
}

โครงสร้างหน่วยความจำฮีป

ฮีปถูกแบ่งออกเป็นส่วนต่างๆ ซึ่งช่วยให้สามารถจัดสรรและล้างข้อมูลได้ง่าย เอเดน พื้นที่คือสถานที่ที่มีการจัดสรรวัตถุใหม่ๆ เมื่ออีเดนกำลังจะถึงขีดจำกัด จะมี GC รอง ซึ่งจะเคลียร์วัตถุที่มีอยู่สำหรับ GC จากพื้นที่ Eden ไปยังหนึ่งใน พื้นที่ผู้รอดชีวิต(S0 และ S1) นอกจากนี้ วัตถุที่มีให้สำหรับ GC ในช่องผู้รอดชีวิตช่องใดช่องหนึ่งจะถูกเคลียร์ และวัตถุที่เหลือจะย้ายไปยังช่องผู้รอดชีวิตช่องอื่น ดังนั้นในแต่ละครั้งจะมีพื้นที่ว่างของผู้รอดชีวิตหนึ่งแห่งเสมอ หลังจาก n (ขึ้นอยู่กับการใช้งาน JVM) ออบเจ็กต์การวนซ้ำดังกล่าวซึ่งยังคงอยู่จะถูกย้ายไปยัง หน่วยความจำเก่า เมื่อใดก็ตามที่หน่วยความจำเก่าจวนจะเป็น Major GC เต็มรูปแบบเกิดขึ้น และทรัพยากรที่ไม่ได้ใช้ของหน่วยความจำเก่าจะถูกล้างออกไป

การเก็บขยะเกิดขึ้นได้อย่างไร

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

ประเภทของการเก็บขยะ

  • อัลกอริธึมการทำเครื่องหมายและการกวาดใช้สำหรับ GC ส่วนใหญ่ - ระยะการทำเครื่องหมาย และ ระยะการกวาด
  • Serial GC— ทำเครื่องหมายและกวาดสำหรับ Minor และ Major GC เกลียวเดี่ยว
  • GC แบบขนาน — เธรด N ถูกสร้างขึ้นสำหรับ GC รอง
  • Parallel Old GC— หลายเธรดสำหรับทั้ง Minor และ Major GC
  • กวาดล้างเครื่องหมายพร้อมกัน (Parallel New GC) — คล้ายกับ GC แบบขนาน ลดการหยุดชั่วคราวของแอปพลิเคชัน ใช้งานได้ส่วนใหญ่กับเธรดของแอปพลิเคชัน
  • G1 GC — ใช้สำหรับฮีปขนาดใหญ่ แยกฮีปออกเป็นขอบเขตและรวบรวมภายในฮีปแบบขนาน

เราสามารถจัดเตรียมอาร์กิวเมนต์ที่จุดเริ่มต้นของ JVM เพื่อระบุ GC ที่จะใช้ ตัวอย่างบางส่วนได้แก่:

ประเด็นที่สำคัญ

  • จำกัดขอบเขตของตัวแปร
  • เริ่มต้นเมื่อคุณต้องการจริงๆ
  • หลีกเลี่ยงการเริ่มต้นแบบวนซ้ำ — ใช้เวลา GC
  • อ้างอิงถึง null อย่างชัดเจน
  • หลีกเลี่ยงการเข้ารอบสุดท้าย ต้องการการอ้างอิง Phantom สำหรับการล้างข้อมูล
  • กำหนดค่า JVM ตามความต้องการโดยการระบุอาร์กิวเมนต์

ไชโย !!!