Tutorial

รู้จักกับ Garbage Collection ใน Java

By Arnon Puitrakul - 17 มกราคม 2016

รู้จักกับ Garbage Collection ใน Java

เมื่อตอนที่ผมย้ายจากเขียน C แล้วมาเขียน Java ก็ทำให้สงสัยอยู่เรื่องนึงว่า "ใน Java มันมีคำสั่งที่ทำงานกับ Memory โดยตรงมั้ย ?" คำตอบที่ได้มาคือ ไม่มี เพราะ Principle ของตัว Java บอกว่า Robust + Secure เพราะฉะนั้น การที่ Java อนุญาติให้โปรแกรมเมอร์สามารถเข้าถึง Memory ได้โดยตรงอาจจะทำให้โปรแกรมของเรา พัง ได้ ดังนั้นตัว Java จึงต้องมีระบบตัวนึงที่เข้ามาช่วย Allocate และ Release Memory มันมีชื่อว่า Garbage Collection แต่ก่อนอื่นมาดูกันก่อนว่าปกติโปรแกรมที่รันผ่าน JVM จะจัดการ Memory ยังไง

JVM จัดการกับ Memory ยังไง ?

ก่อนที่เราจะไปรู้กันว่า รถขยะ ของเราทำงานกันอย่างไร เราจะต้องมารู้กันก่อนว่า ปกติแล้ว JVM จัดการ Memory ในเครื่องเวลารันอย่างไร
จากรูปด้านล่างเป็น Model คร่าว ๆ ในการ Allocate Memory ใน JVM โดยแบ่งออกเป็น 3 ส่วนใหญ่ ๆ นั่นคือ Heap, PremGen (Permanent Generation) และ Thread เรามาค่อย ๆ ดูกันทีล่ะส่วนกันดีกว่า

Garbage Collection 1

Heap เป็นส่วนที่เก็บ Object ที่สร้างมาจาก Class ต่าง ๆ ที่เราสั่งมันสร้างมาจากใน Code โดยจะแบ่งได้เป็นอีก 2 ประเภทย่อย นั่นคือ Old Generation และ New Generation โดยเราจะมาพูดถึงกันอีกครั้งตรง Garbage Collection เลยล่ะกัน เพราะว่า มันคือพระเอกของเราในวันนี้
PremGen หรือเราจะเรียกเต็ม ๆ ว่า Permanent Generation เป็นส่วนที่ใช้เก็บ แม่พิมพ์ ต่าง ๆ หรือ Class เพื่อรอให้โปรแกรมของเราเรียกใช้นั่นเอง โดยตัวมันเองอนุญาตให้ Thread อื่น ๆ สามารถเข้าถึงได้อีกด้วย (แหงแหละ ถ้า Thread อื่นเข้าถึงไม่ได้แล้วจะ ใช้ยังไง ?)
Thread อันนี้เป็น ส่วนของ Memory ที่ค่อนข้างที่จะเป็นส่วนตัวกว่าคนอื่นเขานิดนึง เพราะว่ามันจะเก็บข้อมูลที่ใช้ใน Thread ของตัวเองเท่านั้น ใครก็ห้ามยุ่ง Thread ใคร Thread มัน

Garbage Collection คืออะไร ?

มาถึงพระเอกของเราในวันนี้กันสักที เจ้าตัว Garbage Collection มันเป็นระบบตัวนึงที่อาศัยอยู่ใน JVM (Java Virtual Mechine) ที่คอยทำหน้าที่เป็น รถขยะ (ต่อจากนี้จะเรียกว่า รถขยะล่ะกันนะ !) มันจะคอยเก็บตัวแปร หรือ Object ที่ไม่ได้ถูกใช้แล้วออกไป เช่นถ้าโปรแกรมเรารัน Method นึงแล้วมี Local Variable อยู่ตัวนึง พอ Method นั้นรันจบ Garbage Collection ก็จะมารับ Local Variable ตัวนั้นออกไปและทำการคืนหน่วยความจำกลับสู่ระบบนั่นเอง
ขั้นตอนการทำงานของมันง่ายมาก ๆ อารมณ์เหมือนเครื่องกรองน้ำที่เขาเอามาขายกันเลย เริ่มแรกโปรแกรมของเราทำการสร้าง Object ขึ้นมา โดย Object ที่พึ่งสร้างไปก็จะอาศัยอยู่ในส่วนของ New Generation และก็จะเป็นแบบนี้เรื่อยไป
จนกระทั่งส่วนของ New Generation เต็มทีนี้ล่ะ Garbage Collection พระเอกของเราก็จะมาทำการเก็บกวาด Memory ที่อยู่ใน New Generation ออกไป โดยมันเข้าไปดูว่า Memory อันไหนที่ไม่ได้ใช้แล้วมันก็จะคืนให้ระบบทิ้งไป และส่วนที่ยังใช้อยู่มันก็จะย้ายไปที่ Old Generation ทันที เรื่องคร่าว ๆ ก็ประมาณนี้ล่ะ

ถังขยะนรกนี่จะทำงานเมื่อไหร่ ?

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

สรุป

วันนี้เราก็ได้มาทำความรู้จักกับการจัดสรรหน่วยความจำใน JVM กันไป และรู้เพิ่มอีกอย่างว่า มันมีระบบอยู่ระบบนึงคอยจัดการกับ Memory ให้เราอยู่ข้างหลังนั่นคือ Garbage Collection ซึ่งเป็นระบบที่สำคัญมากกับการพัฒนาโปรแกรมขนาดใหญ่ในองค์กร หรือ แม้แต่ Android Application ด้วยก็เช่นกัน ถ้าไม่รู้เรื่องนี้ก็สามารถทำให้ App ที่เราสร้างออกมานั้นบรรลัยได้ ดันนั้นเรื่องนี้ถือว่าเป็นเรื่องที่มีความสำคัญมาก มาก มาก ใส่ดอกจันทร์สัก 20 ดอกได้เลย เพราะสำคัญจริง ๆ ในตอนหน้า เราจะมาดูกันว่า ลึก ๆ การทำงานของมันทำงานยังไง และเราจะนำสิ่งเหล่านี้มาทำให้โปรแกรมของเราดีขึ้นได้อย่างไร สำหรับวันนี้ สวัสดีครับ
สามารถดูข้อมูลเพิ่มเติมได้จาก Document ของ Java ตามลิงค์ด้านล่างได้เลย
Understanding Memory Management - Oracle

Read Next...

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

เวลาเราทำงานกับข้อมูลอย่าง Pandas DataFrame หนึ่งในงานที่เราเขียนลงไปให้มันทำคือ การ Apply Function เข้าไป ถ้าข้อมูลมีขนาดเล็ก มันไม่มีปัญหาเท่าไหร่ แต่ถ้าข้อมูลของเราใหญ่ มันอีกเรื่องเลย ถ้าเราจะเขียนให้เร็วที่สุด เราจะทำได้โดยวิธีใดบ้าง วันนี้เรามาดูกัน...

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

Python เป็นภาษาที่เราใช้งานกันเยอะมาก ๆ เพราะความยืดหยุ่นของมัน แต่ปัญหาของมันก็เกิดจากข้อดีของมันนี่แหละ ทำให้เมื่อเราต้องการ Performance แต่ถ้าเราจะบอกว่า เราสามารถทำได้ดีทั้งคู่เลยละ จะเป็นยังไง เราขอแนะนำ Numba ที่ใช้งาน JIT บอกเลยว่า เร็วขึ้นแบบ 700 เท่าตอนที่ทดลองกันเลย...

Humanise the Number in Python with "Humanize"

Humanise the Number in Python with "Humanize"

หลายวันก่อน เราทำงานแล้วเราต้องการทำงานกับตัวเลขเพื่อให้มันอ่านได้ง่ายขึ้น จะมานั่งเขียนเองก็เสียเวลา เลยไปนั่งหา Library มาใช้ จนไปเจอ Humanize วันนี้เลยจะเอามาเล่าให้อ่านกันว่า มันทำอะไรได้ แล้วมันล่นเวลาการทำงานของเราได้ยังไง...

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

การทำงานกับตัวเลขทศนิยมบนคอมพิวเตอร์มันมีความลับซ่อนอยู่ เราอาจจะเคยเจอเคสที่ เอา 0.3 + 0.6 แล้วมันได้ 0.899 ซ้ำไปเรื่อย ๆ ไม่ได้ 0.9 เพราะคอมพิวเตอร์ไม่ได้มองระบบทศนิยมเหมือนกับคนนั่นเอง บางตัวมันไม่สามารถเก็บได้ เลยจำเป็นจะต้องประมาณเอา เราเลยเรียกว่า Floating Point Approximation...