Heap Dump (การดัมพ์ฮีป) คือภาพถ่ายของหน่วยความจำแบบไดนามิกของแอปพลิเคชันที่มีข้อมูลที่สมบูรณ์เกี่ยวกับออบเจ็กต์ที่ยังมีชีวิตทั้งหมด: คลาส ขนาด การอ้างอิงซึ่งกันและกัน และการเข้าถึงได้จากราก GC Heap Dump เป็นเครื่องมือหลักในการวิเคราะห์การรั่วไหลของหน่วยความจำและการปรับปรุงการใช้ทรัพยากรให้เหมาะสม ตาม Android Developers การวิเคราะห์ heap dump สามารถตรวจจับการรั่วไหลของหน่วยความจำได้ถึง 95% รวมถึงการอ้างอิงแบบวนซ้ำ listener ที่ถูกลืม และการอ้างอิงแบบสแตติกที่ไม่ได้รับการปลดปล่อย
ประเด็นสำคัญ
Heap dump คือการดัมพ์ฮีปของเครื่องเสมือนอย่างสมบูรณ์ — พื้นที่หน่วยความจำที่ออบเจ็กต์ที่สร้างขึ้นแบบไดนามิกทั้งหมดอาศัยอยู่ ใน Java และ Kotlin คือฮีป Dalvik/ART บน Android ใน Swift และ Objective-C คือฮีปที่จัดการโดย ARC บน iOS Heap dump จะบันทึกทุกออบเจ็กต์ คลาส ขนาด ฟิลด์ การอ้างอิงไปยังออบเจ็กต์อื่น และแฟล็กการเข้าถึงได้จากราก GC (ตัวแปรสแต็ก ฟิลด์สแตติก การอ้างอิง JNI)
วัตถุประสงค์หลักของ heap dump คือ การตรวจจับการรั่วไหลของหน่วยความจำ การรั่วไหลเกิดขึ้นเมื่อแอปพลิเคชันยังคงถือการอ้างอิงไปยังออบเจ็กต์ที่ไม่จำเป็นอีกต่อไป ซึ่งป้องกันไม่ให้ถูกเก็บโดยตัวเก็บขยะ สาเหตุทั่วไป: listener เหตุการณ์ที่ไม่ได้รับการยกเลิกการลงทะเบียนเมื่อ activity ถูกทำลาย; singleton ที่มีการอ้างอิงไปยังบริบท; closure ที่จับ self; คอลเลกชันสแตติกที่มีการเพิ่มข้อมูลโดยไม่มีการลบ Heap dump ให้ภาพที่แม่นยำ: ออบเจ็กต์ใด “ยังมีชีวิต” ออบเจ็กต์ใดไม่จำเป็น และใครกำลังอ้างอิงถึงออบเจ็กต์เหล่านั้น
ตาม Google I/O รายงานการขัดข้องของแอป Android มากกว่า 60% เกี่ยวข้องกับ OutOfMemoryError และใน 80% ของกรณี สาเหตุหลักคือการรั่วไหลของหน่วยความจำที่ตรวจพบได้ผ่าน heap dump สำหรับแอป iOS สถานการณ์คล้ายกัน: การรั่วไหลเนื่องจาก retain cycles เป็นหนึ่งในสาเหตุที่พบบ่อยที่สุดของการขัดข้อง ซึ่งระบุผ่านเครื่องมือ Allocations ใน Xcode
ควรทำ heap dump เมื่อมีอาการต่อไปนี้: แอปพลิเคชันใช้หน่วยความจำแบบเชิงเส้นระหว่างการดำเนินการซ้ำ ๆ; หลังจากปิดหน้าจอ หน่วยความจำไม่กลับสู่ระดับพื้นฐาน; เกิด OutOfMemoryError หรือคำเตือนหน่วยความจำบน iOS; แอปพลิเคชันยุติลงเนื่องจากการเกินขีดจำกัดหน่วยความจำ การเก็บ heap dump เป็นประจำเป็นส่วนหนึ่งของโปรโตคอลวัฒนธรรมวิศวกรรมในโครงการมือถือขนาดใหญ่เช่น Instagram และ Spotify
Android Studio มี Memory Profiler — เครื่องมือในตัวสำหรับจับภาพ heap dump แบบเรียลไทม์ สามารถเข้าถึงได้ผ่าน View → Tool Windows → Profiler หลังจากเปิดแอปพลิเคชัน ให้เลือกเซสชัน ไปที่แท็บ Memory แล้วคลิก Dump Java Heap Android Studio จะหยุดแอปพลิเคชันชั่วคราว ทำการดัมพ์ฮีป ART และโหลดผลลัพธ์สำหรับการวิเคราะห์ ไฟล์ดัมพ์อยู่ในรูปแบบ .hprof — มาตรฐาน HPROF ที่เข้ากันได้กับเครื่องวิเคราะห์หน่วยความจำส่วนใหญ่
หลังจากโหลดดัมพ์ Android Studio จะแสดงตารางออบเจ็กต์พร้อมคอลัมน์: Allocations (จำนวนอินสแตนซ์), Native Size (หน่วยความจำนอกฮีป ART), Shallow Size (หน่วยความจำของออบเจ็กต์เอง), Retained Size (หน่วยความจำของออบเจ็กต์รวมถึงกราฟย่อยทั้งหมด) การกรองตามชื่อคลาส การเรียงลำดับตาม retained size และการค้นหาตามแพ็คเกจช่วยให้คุณค้นหาพื้นที่ที่มีปัญหาได้อย่างรวดเร็ว
// การรั่วไหลทั่วไป — listener ที่ไม่ได้ยกเลิกการลงทะเบียนใน onDestroy
class MainActivity : AppCompatActivity() {
private val sensorManager by lazy {
getSystemService(SENSOR_SERVICE) as SensorManager
}
private val listener = MySensorListener()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sensorManager.registerListener(listener,
sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT),
SensorManager.SENSOR_DELAY_NORMAL)
}
override fun onDestroy() {
super.onDestroy()
// ❌ ขาด sensorManager.unregisterListener(listener)
// → Activity จะไม่ถูก GC, heap dump จะแสดงการรั่วไหล
}
}
แท็บ Dominator Tree แสดงออบเจ็กต์ที่ยึดหน่วยความจำจำนวนมากที่สุด หากออบเจ็กต์ถูกลบออกจาก dominator tree หน่วยความจำทั้งหมดที่มันยึดไว้จะพร้อมใช้งานสำหรับการเก็บขยะ นี่คือเครื่องมือสำคัญ: แทนที่จะสแกนออบเจ็กต์นับพัน คุณมุ่งเน้นไปที่ 10–20 ออบเจ็กต์ที่ควบคุม 80–90% ของหน่วยความจำ ตาม Google การวิเคราะห์ dominator tree เป็นวิธีที่มีประสิทธิภาพที่สุดในการหาจุดรั่วไหล ซึ่งลดเวลาในการวิเคราะห์จากชั่วโมงเป็นนาที
Xcode Instruments มีเครื่องมือสองอย่างสำหรับทำงานกับ heap dump: Allocations — การจับภาพดัมพ์ฮีปพร้อมกราฟการใช้แบบเรียลไทม์; Leaks — การค้นหารั่วไหลอัตโนมัติผ่านการวิเคราะห์ retain cycles Allocations แสดงออบเจ็กต์ทั้งหมดในฮีป ขนาด จำนวนการสร้าง (allocations) และการปลดปล่อย (deallocations) ความแตกต่างระหว่างจำนวนการสร้างและการปลดปล่อยสำหรับคลาสเฉพาะบ่งชี้ถึงการรั่วไหลที่อาจเกิดขึ้น
การจับภาพ heap dump ใน Allocations ทำได้ด้วยปุ่ม Snapshot Memory — เครื่องมือจะหยุดแอปพลิเคชันชั่วคราวและทำการดัมพ์แบบสมบูรณ์ หลังจากนั้น มุมมองมาตรฐานจะพร้อมใช้งาน: รายการออบเจ็กต์ตามคลาส ต้นไม้การเรียก (call tree) สำหรับแต่ละออบเจ็กต์ และตัวสร้างรายงาน แตกต่างจาก Android Studio ตรงที่ Xcode ไม่ใช้ .hprof แต่เก็บข้อมูลในรูปแบบ .trace ของตัวเองที่เข้ากันได้กับ Instruments
// การรั่วไหลของ iOS ทั่วไป — retain cycle ผ่าน closure
class NetworkManager {
var onComplete: ((Data) -> Void)?
func startRequest() {
// ❌ Closure จับ self — retain cycle
onComplete = { data in
self.process(data)
}
}
func process(_ data: Data) {}
}
เครื่องมือ Leaks ตรวจจับ retain cycles และการรั่วไหลโดยอัตโนมัติผ่านการวิเคราะห์กราฟการอ้างอิง มันทำเครื่องหมายออบเจ็กต์ที่รั่วไหลด้วยไอคอนสีม่วงและแสดงเส้นทางไปยังราก (GC root) เพื่อกำจัด retain cycle เพียงเพิ่ม [weak self] หรือ [unowned self] ในการจับภาพ closure การรันเครื่องมือ Leaks เป็นประจำเป็นขั้นตอนบังคับของไปป์ไลน์ CI ในทีมที่ใช้ Swift สำหรับการพัฒนา iOS
// การแก้ไข — การอ้างอิงแบบอ่อนไปยัง self
onComplete = { [weak self] data in
guard let self else { return }
self.process(data)
}
เพื่อวิเคราะห์ heap dump อย่างถูกต้อง จำเป็นต้องเข้าใจหน่วยวัดสำคัญสามประการ Shallow size คือปริมาณหน่วยความจำที่ออบเจ็กต์ครอบครองโดยตรง: ฟิลด์ ส่วนหัว และการจัดตำแหน่ง สำหรับออบเจ็กต์ Java/Kotlin ทั่วไป shallow size คือ 16–40 ไบต์ Retained size คือ shallow size ของออบเจ็กต์บวกกับผลรวม shallow size ของออบเจ็กต์ทั้งหมดที่สามารถเข้าถึงได้ผ่านออบเจ็กต์นี้เท่านั้น Retained size แสดงผลกระทบที่แท้จริงของออบเจ็กต์ต่อการใช้หน่วยความจำ
| หน่วยวัด | คำอธิบาย | ตัวอย่าง |
|---|---|---|
| Shallow size | ขนาดของออบเจ็กต์本身เป็นไบต์ | Bitmap (100×100) = 40,016 B |
| Retained size | Shallow size + ทุกสิ่งที่มันยึดไว้ | Activity พร้อม View Tree = 2–5 MB |
| Deep size | Retained size + ออบเจ็กต์ที่ซ้อนจากกราฟอื่น | ScrollView พร้อมอะแดปเตอร์ = 10–50 MB |
Dominator tree คือโครงสร้างที่แต่ละออบเจ็กต์อ้างอิงถึง “ผู้ครอบงำ” ของมัน — ออบเจ็กต์ที่ควบคุมการเข้าถึงได้ของมัน หากผู้ครอบงำถูกลบ ออบเจ็กต์ทั้งหมดในต้นไม้ย่อยของมันจะกลายเป็นขยะ การวิเคราะห์ dominator tree เป็นวิธีที่เร็วที่สุดในการค้นหาว่าออบเจ็กต์ใดยึดหน่วยความจำมากที่สุด ตาม Eclipse MAT 90% ของการรั่วไหลถูกตรวจพบโดยการตรวจสอบ top-20 dominator tree ภายใน 5 นาที
กระบวนการวิเคราะห์การรั่วไหลผ่าน heap dump ประกอบด้วยหลายขั้นตอน ขั้นตอนที่ 1: ดำเนินการที่ควรจะปลดปล่อยหน่วยความจำ ขั้นตอนที่ 2: เรียก GC และทำ heap dump ขั้นตอนที่ 3: ค้นหาออบเจ็กต์ที่ควรถูกทำลาย ขั้นตอนที่ 4: สำหรับออบเจ็กต์ที่น่าสงสัย ให้รัน Path to GC Roots — ห่วงโซ่การอ้างอิงที่ทำให้ออบเจ็กต์ยังมีชีวิตอยู่ การอ้างอิงสุดท้ายในห่วงโซ่คือสาเหตุของการรั่วไหล
ฟังก์ชัน Path to GC Roots มีอยู่ใน Android Studio Profiler, Eclipse MAT และ Xcode Instruments มันแสดงห่วงโซ่การอ้างอิงที่สั้นที่สุดจากราก GC ไปยังออบเจ็กต์ที่มีปัญหา โดยการยกเว้นการอ้างอิงแบบอ่อน (weak) และแบบนิ่ม (soft) คุณจะได้เฉพาะการอ้างอิงแบบแข็ง (strong) — การอ้างอิงที่ป้องกันการเก็บขยะจริง ๆ ตาม Square Engineering 70% ของการรั่วไหลในแอป Android เกิดจากเพียงสองรูปแบบ: การอ้างอิงแบบสแตติกไปยัง Activity หรือ Context และ listener ที่ลงทะเบียนแล้วแต่ไม่ได้ยกเลิกการลงทะเบียน
// ตัวอย่างการรั่วไหลผ่านการอ้างอิงแบบสแตติก
object AppCache {
private val cache = mutableMapOf<String, Any>()
fun storeActivityReference(activity: Activity) {
cache["current_activity"] = activity // ❌ การรั่วไหล!
}
}
// การแก้ไข: การอ้างอิงแบบอ่อน
object AppCacheFixed {
private val cache = mutableMapOf<String, WeakReference<Any>>()
}
เทคนิค โหมดเปรียบเทียบ เป็นหนึ่งในวิธีการที่มีประสิทธิภาพมากที่สุดในการค้นหารั่วไหล ทำ heap dump ก่อนและหลังการดำเนินการซ้ำ ๆ เปรียบเทียบจำนวนอินสแตนซ์ของคลาสสำคัญ: หากจำนวน Activity เพิ่มขึ้นแม้ว่า activity ทั้งหมดจะถูกปิด — นั่นคือการรั่วไหล Android Studio และ Eclipse MAT รองรับการเปรียบเทียบดัมพ์อัตโนมัติพร้อมการเน้นความแตกต่าง ตาม Google การเปรียบเทียบดัมพ์ช่วยให้พบการรั่วไหลที่มองไม่เห็นในการวิเคราะห์ครั้งเดียวเนื่องจากผลสะสม
จากการวิเคราะห์ heap dump ในโครงการจริง ได้พัฒนาแนวทางปฏิบัติที่พิสูจน์แล้วสำหรับการปรับหน่วยความจำให้เหมาะสม ใช้ WeakReference สำหรับแคช การเรียกกลับ และการอ้างอิงบริบทในออบเจ็กต์ที่มีอายุยืน ยกเลิกการลงทะเบียน listener ใน onPause/onDestroy สำหรับ Android และ deinit สำหรับ iOS หลีกเลี่ยงคอลเลกชันสแตติกขนาดใหญ่ — หากจำเป็น ให้ใช้ LruCache ด้วยขีดจำกัดขนาด ปรับ Bitmap ให้เหมาะสม: โหลดภาพด้วย inSampleSize ที่ถูกต้อง ใช้ Glide หรือ Picasso พร้อมแคชดิสก์
รวมการจับภาพ heap dump เป็นประจำใน ไปป์ไลน์ CI ของคุณ ตั้งค่างานที่รันการทดสอบ UI ที่ถูกตรวจจับ ดำเนินการสถานการณ์ผู้ใช้หลัก และเปรียบเทียบ heap dump กับเส้นพื้นฐาน หาก retained size เพิ่มขึ้นมากกว่า 5% จากเส้นพื้นฐาน บิลด์จะถูกทำเครื่องหมายเป็นการถดถอย แนวทางนี้ปฏิบัติใน Airbnb, Uber และบริษัทอื่น ๆ ที่มีข้อกำหนดด้านคุณภาพสูง ตาม Uber Engineering การนำการวิเคราะห์ heap dump อัตโนมัติใน CI ไปใช้ลดบักที่เกี่ยวข้องกับหน่วยความจำลง 70% ในหนึ่งไตรมาส
// ตัวอย่างงาน Gradle สำหรับ heap dump อัตโนมัติใน CI
task profileMemory(type: Exec) {
commandLine 'adb', 'shell',
'am start -n com.example/.MainActivity'
// กำลังรอการโหลด
doLast {
exec { commandLine 'adb', 'shell',
'am broadcast -a com.example.DUMP_HEAP' }
}
}
คำถามที่พบบ่อย
Shallow size คือขนาดของออบเจ็กต์本身 (ฟิลด์ + ส่วนหัว) Retained size คือขนาดของออบเจ็กต์บวกกับออบเจ็กต์ทั้งหมดที่จะกลายเป็นขยะหากมันถูกลบ Retained size เป็นตัวบ่งชี้หลักของผลกระทบของออบเจ็กต์ต่อการใช้หน่วยความจำ
ผ่าน Android Studio Profiler เลือกอุปกรณ์และกระบวนการ คลิก Dump Java Heap หรือผ่านบรรทัดคำสั่ง: adb shell am dumpheap PID /sdcard/dump.hprof จากนั้น adb pull
Heap dump รวมออบเจ็กต์ที่ ยังมีชีวิต ทั้งหมด หากแอปพลิเคชันใช้แคช Bitmap หรือประมวลผลข้อมูลขนาดใหญ่ ดัมพ์อาจถึงหลายร้อยเมกะไบต์ กรองตามคลาสหรือใช้ Eclipse MAT เพื่อโหลดเฉพาะดัชนี
ได้ ใช้ Eclipse MAT (Memory Analyzer Tool) — เครื่องมือฟรีสำหรับวิเคราะห์ไฟล์ .hprof รองรับ dominator tree, path to GC roots, การเปรียบเทียบดัมพ์ และการตรวจจับการรั่วไหลอัตโนมัติผ่าน Leak Suspects Report
ตัวดัมพ์เอง — ใช่ เพราะการเก็บดัมพ์หยุดเธรดทั้งหมด (stop-the-world) ไม่มีดัมพ์ — ไม่ ควรทำดัมพ์ในสภาพแวดล้อมที่ควบคุม (แท่นทดสอบ CI) ไม่ใช่ในระบบผลิต
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม