☕ 5-Min Tech Story #03

ภาพลวงตาของ Garbage Collector: มีคนช่วยเก็บขยะ แล้วทำไมแรมยังเต็มจน Server ดับ?

⏱️ ใช้เวลาอ่านประมาณ: 4 นาที 30 วินาที

สาย C หรือ C++ มักจะมองชาว Java / Kotlin ด้วยสายตาอิจฉาปนหมั่นไส้ เพราะไม่ต้องมานั่งพิมพ์ malloc() หรือจำว่าต้อง free() คืนแรมตอนไหนให้ปวดกบาล

เราโตมากับคำสอนที่ว่า "เขียนๆ ไปเหอะ JVM มี Garbage Collector (GC) คอยเก็บกวาดขยะให้อัตโนมัติ สบายจะตาย"

แต่ถ้ามันแสนดีขนาดนั้น... ทำไมตอนระบบรันไปสัก 3 วัน กราฟแรมถึงค่อยๆ ไต่ขึ้นเป็นบันไดสวรรค์ แล้วจบลงด้วยหายนะระดับชาติ:

java.lang.OutOfMemoryError: Java heap space

🧹 GC ไม่ใช่คนฉลาดรู้ใจ แต่เป็น "พนักงานทำความสะอาดตามกฎ"

นิยามคำว่า "ขยะ" ของมนุษย์เราคือ "ของที่เราไม่ได้ใช้แล้ว"
แต่นิยามคำว่า "ขยะ" ของ Garbage Collector คือ "ของที่ไม่มีทางเอื้อมถึง (Unreachable)"

ลองจินตนาการว่าคุณจ้างแม่บ้านมาทำความสะอาดห้อง กฎเหล็กของแม่บ้านคือ:

"กล่องอะไรก็ตามที่มีเชือกผูกติดอยู่กับเสากลางบ้าน แม่บ้านจะไม่กล้าทิ้งเด็ดขาด เพราะถือว่าเจ้าของบ้านยังหวงอยู่"

และ "เสากลางบ้าน" ในโลกของ Java ก็คือสิ่งที่เรียกว่า GC Roots (เช่น ตัวแปร Static, Thread ที่กำลังทำงานอยู่ หรือตัวแปร Local ใน Call Stack ปัจจุบัน)

👻 ฆาตกรเงียบ: ผูกเชือกทิ้งไว้โดยไม่รู้ตัว

แล้ว Memory Leak เกิดขึ้นได้ยังไง? ส่วนใหญ่ไม่ได้เกิดจากท่าลึกลับ แต่เกิดจากความชุ่ยแบบเนียนๆ สองสามท่านี้:

1. Static Collection ที่ไม่มีวันหยุดโต

// ทำ In-memory cache แบบบ้านๆ
object UserCache {
  val cache = mutableMapOf<String, BigUserData>()
}

fun onUserLogin(user: BigUserData) {
  UserCache.cache[user.id] = user // ใส่เข้าเรื่อยๆ แต่ไม่เคยลบออก!
}

ตัวแปร Static จะมีชีวิตอยู่เท่ากับอายุของ JVM เชือกเส้นนี้จึงไม่มีวันขาด! ยิ่งมีคนล็อกอิน แรมก็ยิ่งบวม จนสุดท้าย GC ต้องยอมแพ้

2. Listener และ Callback ที่ Register แล้วไม่ยอม Unregister

คุณสร้าง Object ชั่วคราวขึ้นมาทำงาน 3 วินาที แต่เผลอเอาไปผูกไว้กับ Event Publisher กลางของระบบ... ผลคือตัวแม่ยังถือ Reference ตัวลูกไว้ Object ลูกก็เลยตายไม่ได้ ต้องลอยค้างอยู่ใน Heap Memory ไปตลอดกาล

💡 5-Min Takeaway

Garbage Collector ช่วยปกป้องเราจากการ ลืมคืนแรม แต่ไม่สามารถปกป้องเราจากการ ลืมตัดสายสัมพันธ์ (Reference) ได้ อย่าปล่อยให้ Object ชั่วคราวไปผูกติดกับ Object อายุยืนโดยไม่จำเป็น ถ้าผูกเชือกไว้ เมื่อเลิกใช้ ต้องตัดเชือกทิ้งเสมอ