OutOfMemoryError ในการพัฒนาแอปพลิเคชัน: คืออะไร สาเหตุ และวิธีการป้องกัน

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-29 เวลาอ่าน: 9 นาที

OutOfMemoryError เป็นข้อยกเว้นร้ายแรงที่เกิดขึ้นเมื่อ Java Virtual Machine (JVM) หรือ Android Runtime (ART) ไม่สามารถจัดสรรหน่วยความจำสำหรับออบเจ็กต์ใหม่เนื่องจากพื้นที่ใน Heap ไม่เพียงพอ ตามข้อมูลจาก Square Engineering พบว่า 70% ของ OutOfMemoryError ในแอปพลิเคชันมือถือเกิดจากการรั่วไหลของหน่วยความจำ ไม่ใช่การเกินขีดจำกัดจริง การทำความเข้าใจ สาเหตุของ OOM เป็นกุญแจสำคัญสู่ความเสถียรของแอปพลิเคชัน

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

  • OutOfMemoryError — ข้อยกเว้นเมื่อ Heap ไม่เพียงพอสำหรับสร้างออบเจ็กต์ใหม่
  • Heap — พื้นที่หน่วยความจำที่ออบเจ็กต์ Java/Kotlin ทั้งหมดอาศัยอยู่
  • Bitmap — ผู้บริโภค Heap หลักใน Android แหล่งที่มาทั่วไปของ OOM
  • Heap Dump — ภาพรวมของ Heap สำหรับวิเคราะห์ว่าใครใช้หน่วยความจำเท่าใด
  • การรักษา OOM ต้องการการแก้ไขการรั่วไหลและการปรับปรุงการใช้หน่วยความจำ

OutOfMemoryError คืออะไร

OutOfMemoryError (OOM) เป็นข้อยกเว้นในตระกูล VirtualMachineError ใน Java/Kotlin ที่บ่งชี้ว่าไม่สามารถจัดสรรหน่วยความจำสำหรับออบเจ็กต์ใหม่ได้ ซึ่งแตกต่างจากข้อยกเว้นแบบตรวจสอบได้ OOM เป็น Error และไม่จำเป็นต้องจัดการผ่าน catch — แม้ว่าในทางเทคนิคจะสามารถจับได้ก็ตาม หลังจากเกิด OOM แอปพลิเคชันมักจะอยู่ในสถานะไม่เสถียร และแนะนำให้ยุติการทำงาน

บน Android แต่ละแอปพลิเคชันมีขีดจำกัด Heap ที่กำหนดโดยผู้ผลิตอุปกรณ์ สำหรับสมาร์ทโฟนสมัยใหม่ที่มี RAM 6+ GB ขีดจำกัดคือ 256–512 MB สำหรับอุปกรณ์ระดับประหยัด — 128–192 MB เมื่อปริมาณรวมของออบเจ็กต์ที่มีชีวิตทั้งหมดเกินขีดจำกัดนี้ ART จะโยน OutOfMemoryError

สิ่งสำคัญ คือต้องเข้าใจ: OOM ไม่ได้หมายความว่าอุปกรณ์หมดหน่วยความจำจริงเสมอไป หมายความว่าแอปพลิเคชันใช้ขีดจำกัด Heap ที่ระบบกำหนดจนหมดแล้ว แอปพลิเคชันอื่นอาจมีหน่วยความจำว่าง แต่แอปพลิเคชันของคุณไม่สามารถใช้งานได้เนื่องจากการแยกกระบวนการใน Android

สาเหตุหลักของ OutOfMemoryError

ห้าสถานการณ์ นำไปสู่ OOM ในแอปพลิเคชันมือถือเป็นประจำ แต่ละสถานการณ์เกี่ยวข้องกับประเภทข้อมูลหรือการดำเนินการเฉพาะ

Bitmap ไม่มีการปรับขนาด

Bitmap เป็นผู้บริโภคหน่วยความจำหลักในแอปพลิเคชัน Android การโหลดภาพ FullHD (1920 × 1080) ในขนาดดั้งเดิมใช้พื้นที่ 8.3 MB ในรูปแบบ ARGB_8888 หากมีภาพดังกล่าว 50 ภาพใน RecyclerView — นั่นคือ 415 MB ซึ่งเกิน Heap ของอุปกรณ์ใดๆ การโหลดภาพโดยไม่มี inSampleSize รับประกัน OOM บนอุปกรณ์ที่อ่อนแอ

ใช้ Glide หรือ Coil สำหรับการปรับขนาดอัตโนมัติ ไลบรารีเหล่านี้โหลดภาพด้วยขนาดที่ตรงกับ View ไม่ใช่ความละเอียดดั้งเดิม สำหรับการใช้ BitmapFactory.Options โดยตรง ให้ใช้ inSampleSize: คำนวณเป็นเลขยกกำลังสองเพื่อให้ขนาดสุดท้ายไม่เกิน 2048 × 2048 พิกเซล นอกจากนี้ ใช้ RGB_565 แทน ARGB_8888 สำหรับภาพที่ไม่มีความโปร่งใส — ซึ่งจะลดการใช้หน่วยความจำลงครึ่งหนึ่ง

kotlin
fun loadScaledBitmap(path: String, reqWidth: Int): Bitmap? {
    val opts = BitmapFactory.Options().apply {
        inJustDecodeBounds = true
    }
    BitmapFactory.decodeFile(path, opts)
    opts.inSampleSize = calculateSampleSize(opts.outWidth, reqWidth)
    opts.inJustDecodeBounds = false
    return BitmapFactory.decodeFile(path, opts)
}

การรั่วไหลของหน่วยความจำ (การสะสม)

การรั่วไหล เพียงครั้งเดียวสองสาม KB จะไม่ทำให้เกิด OOM แต่การรั่วไหลหลายสิบครั้งบนแต่ละหน้าจอจะสะสม: การเปลี่ยนหน้าจอแต่ละครั้งเพิ่มการรั่วไหล GC ไม่สามารถปลดปล่อยออบเจ็กต์ และ Heap เต็ม รูปแบบทั่วไป: ผู้ใช้เปิดและปิดหน้าจอโปรไฟล์ 20 ครั้ง → Heap เพิ่มขึ้น 200 MB → แอปพลิเคชันล้มเหลวด้วย OOM

ติดตั้ง LeakCanary ในโปรเจกต์เพื่อตรวจจับการรั่วไหลอัตโนมัติ มันจะแสดงออบเจ็กต์ที่รั่วไหลแต่ละรายการพร้อมร่องรอยสแต็กที่แน่นอน หลังจากแก้ไขการรั่วไหลทั้งหมด การใช้ Heap จะคงที่: หลังจากปิดหน้าจอ หน่วยความจำจะกลับสู่ระดับพื้นฐาน

ไฟล์ขนาดใหญ่ในหน่วยความจำ

การโหลด ไฟล์ทั้งหมดลงใน byte[] เป็นเส้นทางตรงสู่ OOM ไฟล์ JSON ขนาด 50 MB ระหว่างการแยกวิเคราะห์จะสร้างสตริงที่มีขนาดเท่ากันบวกกับโมเดล DOM ไฟล์วิดีโอที่โหลดในหน่วยความจำ บัฟเฟอร์เสียง และชุดข้อมูล protobuf ขนาดใหญ่ — ทั้งหมดสามารถเกินขีดจำกัด Heap ในการดำเนินการเดียว

ประมวลผลข้อมูลขนาดใหญ่โดยใช้ สตรีม: InputStream พร้อมบัฟเฟอร์ 4–8 KB, ตัวแยกวิเคราะห์ JSON แบบสตรีมมิ่ง (Jackson หรือ Gson กับ JsonReader), MediaCodec สำหรับวิดีโอ อย่าเรียก File.readBytes() บนไฟล์ที่มีขนาดใหญ่กว่า 10% ของ Heap ที่มีอยู่

การสร้างออบเจ็กต์จำนวนมากในลูป

การสร้าง ออบเจ็กต์อย่างหนักในลูปโดยไม่มี GC ระหว่างกลางสามารถนำไปสู่ OOM โดยเฉพาะบนอุปกรณ์ที่มี Heap ขนาดเล็ก ตัวอย่าง: การสร้าง 100,000 ออบเจ็กต์ใน for-loop ที่ไม่พอดีใน Heap ก่อนที่ GC จะรวบรวมได้ พบได้บ่อยในเกมและโปรแกรมแก้ไขกราฟิก

ใช้ Object Pool สำหรับออบเจ็กต์ที่ถูกสร้างและทำลายจำนวนมาก สำหรับข้อมูลตัวเลข ให้ใช้ชนิดดั้งเดิม (FloatArray แทน List<Float>) RecyclerView กับ ViewHolder Pool แก้ปัญหานี้สำหรับคอมโพเนนต์ UI

การแตกกระจายของ Heap

การแตกกระจาย เป็นสถานะที่มีหน่วยความจำว่างเพียงพอโดยรวม แต่ไม่มีบล็อกต่อเนื่องสำหรับออบเจ็กต์ใหม่ ART บีบอัด Heap ระหว่าง GC แต่ไม่สำเร็จเสมอไป อาเรย์ขนาดใหญ่ (Bitmap, byte[]) มีความไวต่อการแตกกระจายมากที่สุด

ART บน Android 8+ ใช้ Generational GC ซึ่งลดการแตกกระจายโดยการแยกออบเจ็กต์อายุน้อยและอายุมาก อย่างไรก็ตาม หลีกเลี่ยงการจัดสรรชิ้นส่วนขนาดต่างๆ ในพูลเดียวกัน — พยายามใช้บัฟเฟอร์ที่จัดสรรไว้ล่วงหน้าขนาดคงที่

ขีดจำกัด Heap ใน Android

ขีดจำกัด Heap ใน Android ไม่ใช่ค่าคงที่ — ขึ้นอยู่กับผู้ผลิต รุ่นอุปกรณ์ และเวอร์ชันของระบบปฏิบัติการ Google กำหนดข้อกำหนดขั้นต่ำผ่านเอกสารคำจำกัดความความเข้ากันได้ (CDD) แต่ผู้ผลิตกำหนดค่าจริง

หมวดหมู่อุปกรณ์Heap ทั่วไปlargeHeap
ประหยัด (1–2 GB RAM)128–192 MB256–384 MB
ระดับกลาง (3–4 GB RAM)256–384 MB512 MB
เรือธง (6+ GB RAM)384–512 MB768 MB–1 GB
แท็บเล็ต (4+ GB RAM)256–512 MB768 MB
Wear OS32–64 MBไม่มี

คุณสามารถขอขีดจำกัดที่เพิ่มขึ้นผ่าน android:largeHeap="true" ในแมนิเฟสต์ ใช้ด้วยความระมัดระวัง: การเพิ่ม Heap ไม่ได้แก้ปัญหาการรั่วไหลและอาจทำให้ประสบการณ์ผู้ใช้แย่ลงหากระบบถูกบังคับให้ปิดแอปพลิเคชันอื่นเพื่อเพิ่มหน่วยความจำให้กับแอปของคุณ สำหรับ Wear OS ขีดจำกัด Heap น้อยที่สุด — เพียง 32–64 MB, largeHeap ไม่พร้อมใช้งานที่นี่ และการประหยัดหน่วยความจำสำคัญเป็นสองเท่า

การวินิจฉัย OutOfMemoryError

การวินิจฉัย OOM ต้องวิเคราะห์ Heap Dump และทำความเข้าใจว่าออบเจ็กต์ใดใช้หน่วยความจำ Android Studio มีเครื่องมือที่จำเป็นทั้งหมด

ขั้นตอนที่ 1: จับภาพช่วงเวลาที่เกิด OOM ใน Android Memory Profiler คลิก Record memory allocations และดำเนินการสถานการณ์ที่ทำให้เกิดการล้มเหลว Profiler จะแสดงการเพิ่มขึ้นของการจัดสรรก่อน OOM หาก OOM ไม่สามารถทำซ้ำได้ ให้ลด Heap ผ่าน android:smallHeap ในบิลด์ดีบักหรือใช้ DDMS ด้วยการเรียก GC ด้วยตนเอง

ขั้นตอนที่ 2: ถ่าย Heap Dump ที่โหลดสูงสุด (ก่อน OOM) เปิด Dump ใน Android Studio: แท็บ Classes จะเรียงตาม Retained Size ออบเจ็กต์ที่ใหญ่ที่สุดคือ Bitmap, byte[], String สำหรับแต่ละ Bitmap ให้ตรวจสอบขนาด (กว้าง × สูง × 4 ไบต์) และเส้นทางการโหลดผ่าน Stack Trace

ขั้นตอนที่ 3: วิเคราะห์จำนวนออบเจ็กต์ที่ซ้ำกัน หากคุณเห็น Fragment หรือ Activity ที่เหมือนกัน 200 รายการ — นั่นคือการรั่วไหล หาก Bitmap 500 รายการที่มีขนาดเท่ากัน — นั่นคือปัญหาแคชภาพ MAT (Memory Analyzer Tool) ให้การวิเคราะห์เชิงลึกยิ่งขึ้นด้วย Dominator Tree ที่แสดงว่าออบเจ็กต์ใดถือครอง Heap 80%

text
// คำสั่ง Heap Dump ผ่าน adb
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof.

กลยุทธ์การป้องกัน OOM

กลยุทธ์ การป้องกัน OOM ที่ครอบคลุมประกอบด้วยการป้องกันห้าระดับ: ตั้งแต่การตัดสินใจทางสถาปัตยกรรมไปจนถึงการตรวจสอบในระบบผลิตจริง

การตัดสินใจทางสถาปัตยกรรม

ViewModel + Repository แยกข้อมูลออกจาก UI และป้องกันการเก็บรักษา View เมื่อหมุนหน้าจอ ViewModel มีอายุยืนกว่า Activity ข้อมูลไม่สูญหาย และ View สามารถสร้างใหม่ได้โดยไม่ทำซ้ำข้อมูลในหน่วยความจำ ใช้ StateFlow แทน LiveData สำหรับการจัดการสถานะอย่างชัดเจน

การจัดการ Bitmap และภาพ

Glide เป็นไลบรารีบังคับสำหรับทำงานกับภาพ มันปรับขนาด จัดเก็บแคช (ดิสก์ + หน่วยความจำ) และรีไซเคิล Bitmap โดยอัตโนมัติ กำหนดค่า diskCacheStrategy และ skipMemoryCache สำหรับรายการขนาดใหญ่ สำหรับภาพเคลื่อนไหว ให้ใช้ Glide กับ GIF/WebP — ใช้หน่วยความจำน้อยกว่าลำดับของ Bitmap

การตรวจสอบในระบบผลิตจริง

Firebase Performance Monitoring ติดตามการใช้หน่วยความจำแบบเรียลไทม์ ตั้งค่าการแจ้งเตือนเมื่อการใช้ Heap เกิน 80% ของขีดจำกัด — นั่นคือสัญญาณให้ตรวจสอบ Crashlytics รวบรวม OOM เป็นข้อยกเว้นและแสดงสถานะ Heap ล่าสุดที่ทราบก่อนการล้มเหลว สำหรับ Android 11+ ให้ใช้ ApplicationExitInfo เพื่อตรวจจับการสิ้นสุดจาก OOM

การทดสอบบนอุปกรณ์ที่อ่อนแอ

อย่าลืม ทดสอบแอปพลิเคชันบนอุปกรณ์ที่มี Heap ขั้นต่ำ (128–192 MB) โปรแกรมจำลองที่มีหน้าจอขนาดเล็กและ Heap ขนาดเล็กจะจำลองอุปกรณ์ระดับประหยัด หากแอปพลิเคชันทำงานบนอุปกรณ์ดังกล่าว จะไม่มีปัญหา OOM บนอุปกรณ์เรือธง ใช้ Firebase Test Lab กับอุปกรณ์จริงจากช่วงราคาต่างๆ

kotlin
// การตรวจสอบ Heap ที่มีอยู่ก่อนดำเนินการหนัก
fun canAllocate(requiredBytes: Long): Boolean {
    val runtime = Runtime.getRuntime()
    val free = runtime.freeMemory()
    return free > requiredBytes * 2 // บัฟเฟอร์ 50%
}

คำถามที่พบบ่อย

สามารถจับ OutOfMemoryError ด้วย try-catch ได้หรือไม่?

ในทางเทคนิคได้ แต่ไม่แนะนำ หลังจาก OOM แอปพลิเคชันอยู่ในสถานะไม่เสถียร: การจัดสรรใหม่อาจล้มเหลว และออบเจ็กต์บางส่วนอาจถูกสร้างขึ้นเพียงบางส่วน การดำเนินการที่สมเหตุสมผลเพียงอย่างเดียวใน catch คือการบันทึกและการเริ่ม Activity ใหม่

ทำไม OOM ถึงไม่เกิดขึ้นบนทุกอุปกรณ์?

ขีดจำกัด Heap แตกต่างกันในแต่ละอุปกรณ์ การดำเนินการที่ต้องใช้ 300 MB จะล้มเหลวบนอุปกรณ์ที่มีขีดจำกัด 192 MB แต่จะสำเร็จบนเรือธงที่มี 512 MB ทดสอบบนอุปกรณ์ที่มีสเปกขั้นต่ำเพื่อตรวจจับสถานการณ์ OOM

largeHeap ส่งผลต่อประสิทธิภาพอย่างไร?

largeHeap เพิ่มขีดจำกัดแต่ไม่ทำให้แอปพลิเคชันเร็วขึ้น การหยุดชั่วคราวของ GC นานขึ้นเนื่องจากการรวบรวม Heap ขนาดใหญ่ใช้เวลามากขึ้น ระบบอาจปิดแอปพลิเคชันพื้นหลังเพื่อจัดหาหน่วยความจำ ใช้ largeHeap เฉพาะกับแอปพลิเคชันที่ต้องการหน่วยความจำมากอย่างเป็นกลาง (กล้อง, โปรแกรมแก้ไข)

OOM แตกต่างจากการฆ่าโดยระบบอย่างไร?

OOM เป็นข้อยกเว้นภายในแอปพลิเคชันเมื่อ Heap ไม่เพียงพอ การฆ่าโดยระบบ (Low Memory Killer) เป็นการตัดสินใจของเคอร์เนล Linux ที่จะฆ่าโพรเซสเพื่อเพิ่มหน่วยความจำให้แอปพลิเคชันอื่น ในการฆ่าโดยระบบ แอปพลิเคชันจะไม่ได้รับข้อยกเว้น — โพรเซสเพียงแค่สิ้นสุด

Bitmap ใช้หน่วยความจำจริงเท่าใด?

สูตร: กว้าง × สูง × bytesPerPixel ARGB_8888 = 4 B/พิกเซล, RGB_565 = 2 B/พิกเซล Bitmap FullHD (1920 × 1080) ใน ARGB_8888 = 8.3 MB Bitmap 4K (3840 × 2160) = 33 MB ปรับขนาดภาพให้เป็นขนาดที่จำเป็นสำหรับการแสดงผลบนหน้าจอเสมอ

สรุป

  • OutOfMemoryError — ข้อยกเว้นร้ายแรงเมื่อขีดจำกัด Heap ของแอปพลิเคชันหมด
  • Bitmap ไม่มีการปรับขนาด — ตัวการหลักของ OOM ในแอปพลิเคชันมือถือ
  • การรั่วไหลของหน่วยความจำ ทำให้เกิด 70% ของ OOM ผ่านการสะสมออบเจ็กต์ในแต่ละการเปลี่ยน
  • ขีดจำกัด Heap อยู่ระหว่าง 128 MB บนอุปกรณ์ประหยัดถึง 512 MB บนอุปกรณ์เรือธง
  • Heap Dump พร้อมการวิเคราะห์ Retained Size — เครื่องมือหลักในการวินิจฉัย OOM
  • Glide หรือ Coil เป็นสิ่งจำเป็นสำหรับการทำงานกับภาพทุกขนาด
  • การทดสอบ บนอุปกรณ์ที่มี Heap ขั้นต่ำเป็นสิ่งจำเป็นสำหรับทุกโปรเจกต์

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม