Android 内存泄漏与内存溢出详解与实战分析

139 阅读5分钟

🪄 一、前言

随着 Android 应用功能日益复杂,内存问题几乎无处不在。
一个轻微的内存泄漏可能暂时不会崩溃,但会悄悄积累,最终导致:

  • App 启动变慢
  • 页面切换卡顿
  • 内存持续上涨
  • 最终崩溃:OutOfMemoryError

本文将系统讲解:

  • 什么是内存泄漏与溢出
  • 常见泄漏场景与原理
  • 如何定位与修复
  • 实战优化案例

🧩 二、基础概念区分

✅ 1. 内存泄漏(Memory Leak)

对象 不再被需要,但依然被某处引用,导致 GC 无法回收。

举例:

class LeakActivity : AppCompatActivity() {
    companion object {
        var instance: LeakActivity? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        instance = this // ❌ 静态引用 Activity
    }
}

🔹 问题分析:

  • Activity 销毁后仍被静态变量持有
  • 无法被 GC 回收
  • 多次进入页面 → 内存持续上涨 → 崩溃

❌ 2. 内存溢出(Out Of Memory, OOM)

程序占用的内存超出系统分配上限。

常见错误:

java.lang.OutOfMemoryError: Failed to allocate a 1234567 byte allocation with 512 free bytes

触发条件:

  • 图片、视频、Bitmap 占用过大
  • 循环引用 + 泄漏叠加
  • 加载过多资源或缓存未释放

⚙️ 三、Android 内存管理机制简述

  1. GC(垃圾回收)机制

    • Android 使用 引用计数 + 可达性分析(Reachability Analysis)
    • 当对象不可达时,GC 自动释放其内存
    • 常见算法:标记-清除(Mark-Sweep)、复制算法(Copying)
  2. 内存分配限制

    • 每个 App 都有最大内存上限(如 256MB~512MB)
    • 可通过 Runtime.getRuntime().maxMemory() 查看
  3. 内存回收的盲区

    • GC 只能回收无引用对象
    • 若错误地长期持有引用 → 无法自动释放

🧠 四、常见内存泄漏场景与修复

1️⃣ 静态变量持有 Context / Activity

object Singleton {
    var context: Context? = null
}

➡️ 原因:静态对象生命周期与应用一致,持有 Activity 会阻止回收。

正确写法

object Singleton {
    private lateinit var appContext: Context
    fun init(context: Context) {
        appContext = context.applicationContext // ✅ 使用 ApplicationContext
    }
}


2️⃣ 非静态内部类的 Handler / Runnable

class LeakActivity : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper()) {
        // ...
        true
    }
}

➡️ 原因:Handler 是内部类,默认持有外部类引用。
若消息队列中仍有未处理的 Message,则 Activity 无法释放。

修复方案

class LeakActivity : AppCompatActivity() {
    private val handler = SafeHandler(this)

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }

    private class SafeHandler(activity: LeakActivity) : Handler(Looper.getMainLooper()) {
        private val ref = WeakReference(activity)
        override fun handleMessage(msg: Message) {
            ref.get()?.let {
                // 安全访问 Activity
            }
        }
    }
}

✅ 使用静态内部类 + WeakReference 可避免引用 Activity。


3️⃣ 匿名类、Lambda 闭包引用外部对象

button.setOnClickListener {
    textView.text = "Clicked"  // ❌ 引用了外部 View
}

➡️ 原因:匿名内部类持有外部类引用。
若闭包未释放(如长生命周期任务),会导致泄漏。

解决

  • 避免在延时任务、全局回调中使用 Activity 引用
  • 使用 WeakReference 或回调接口解耦

4️⃣ 单例 / 静态持有 View、Bitmap

object ImageCache {
    val cacheMap = mutableMapOf<String, Bitmap>()
}

➡️ 原因:Bitmap 未释放会导致 native 层泄漏。

修复

fun clear() {
    for (bmp in cacheMap.values) {
        bmp.recycle()
    }
    cacheMap.clear()
}

或使用 LruCache 自动管理缓存回收。


5️⃣ 未关闭的资源

val cursor = db.query("table", null, null, null, null, null, null)
// ❌ 未关闭

修复

cursor.use {
    // 自动关闭
}

同理:

  • FileInputStream
  • Cursor
  • BroadcastReceiver
  • SensorManager / LocationManager

⚠️ 所有“注册类资源”都必须在 onDestroy() 解绑。


🔧 五、定位工具与分析方法

🔹 1. LeakCanary(推荐)

Square 出品的内存泄漏检测神器。

集成步骤:

dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}

使用方式:

运行 App → 退出页面 → LeakCanary 自动检测泄漏
→ 通知栏出现泄漏报告。

报告内容包括:

  • 哪个对象未释放
  • 被哪个引用链持有
  • 泄漏来源类与字段

示例报告:

ActivityLeakActivity has leaked:
┬───
│ GC Root: System class
│ LeakActivity instance
│ ↳ handler (android.os.Handler)
│   ↳ callback (android.os.Message)

🔹 2. Android Studio Profiler

路径:
View → Tool Windows → Profiler → Memory

功能:

  • 实时内存使用图
  • Heap Dump 分析
  • Object Reference 跟踪
  • GC 事件记录

🔹 3. MAT (Memory Analyzer Tool)

Eclipse 提供的专业堆分析工具。
适合分析 .hprof 文件中的复杂泄漏链。


💥 六、内存溢出案例分析

场景:加载超大图片导致 OOM

val bitmap = BitmapFactory.decodeFile(path)
imageView.setImageBitmap(bitmap)

假设原图 4000×4000,ARGB_8888 占用约 64MB → 直接崩溃。

✅ 解决方案:

val options = BitmapFactory.Options().apply {
    inJustDecodeBounds = true
}
BitmapFactory.decodeFile(path, options)

options.inSampleSize = calculateInSampleSize(options, 800, 800)
options.inJustDecodeBounds = false
val bitmap = BitmapFactory.decodeFile(path, options)

核心思想:按需压缩加载(采样率缩放)。


🧰 七、内存优化实战技巧总结

类别优化策略
Context 使用使用 ApplicationContext,避免 Activity 泄漏
Handler/线程使用静态内部类 + WeakReference
图片资源使用 Glide/Picasso 自动回收管理
缓存使用 LruCache 替代静态 Map
生命周期绑定在 onDestroy/onStop 中清理资源
Leak 检测集成 LeakCanary,养成调试习惯
异步任务避免长时间持有 Activity 引用

🧠 八、总结

项目内容
内存泄漏不再需要的对象仍被引用
内存溢出占用内存超过系统限制
检测工具LeakCanary / Profiler / MAT
常见场景Handler、静态引用、闭包、未关闭资源
解决策略弱引用、解绑资源、合理使用 Context
实战重点预防 > 修复;监控 > 临时清理

📦 九、延伸阅读