Android内存泄漏排查篇1(JAVA层泄漏)

发布时间:2026/6/17 3:04:10

Android内存泄漏排查篇1(JAVA层泄漏) 一、java层内存泄漏原理1.1 GC 原理Java通过GC Roots节点到对象的引用链判断对象是否存活如静态变量、活动线程、方法区常量等若对象从GC Roots引用链可达则不会被回收当对象不存在从GC Roots到它的引用链时就会被gc回收。1.2 Java层内存泄漏本质Java层内存泄漏是由于意料之外的对象引用导致GC无法释放其占用的内存与C/C的内存泄漏不同因为Java有GC自动回收机制而我们所说的Java层内存泄漏实际上是我们认为某个对象不需要了但是实际上有意料之外的对象还在引用它造成GC无法释放其占用的内存。属于逻辑上的泄漏。1.3 什么情况会泄漏造成内存泄漏的原因是更长生命周期的对象引用了短生命周期对象导致短生命周期的对象要在长生命周期对象释放后才可以释放。即使我们认为短生命的对象已经不再需要了二、Java层内存泄漏常见案例2.1 静态对象持有Context常见在单例类中如果单例类创建后持有context上下文由于单例类创建后在整个app运行期间一般不会去销毁这样子被持有context的对象就一直不能释放哪怕其已经不会再使用。所以这个被持有context的对象一般是Activity就从逻辑上泄漏了另一种是获取系统服务导致的内存泄漏除了UI类型外的系统服务很多会持有调用它的context像CONNECTIVITY_SERVICE也会导致内存泄漏还有一个是全局的静态容器存储了对象但是未及时清理对象本身有持有context如ListMap之类。2.2非静态内部类/匿名类​​由于非静态内部类、匿名类默认持有外部引用this指针所以只要其创建了并且一直不销毁外部对象引用链就一直是可达的。常见的有几种Handler/Runnable延迟任务、异步任务、线程/计时器一直在运行....Handler发送延迟消息若Activity在消息处理完成前销毁则Handler持有其引用导致泄漏AsyncTask在后台执行时由于持有Activity引用当Activity在任务结束前退出也会导致内存泄漏长期存在的计时器或者线程需要特别注意如果onDestroy没有取消掉也会导致泄漏并且可能是app运行期间都不会恢复的。这里需要提到一个点内存泄漏是否必须处理? 其实在我看来还是和实际场景有关举个例子在活动结束时后台有任务还在运行此时确实会导致活动无法回收但是如果任务执行2s后完成了那么这个时候活动可以被回收这种延时回收的现象是你可以接受的那可以不处理如果是一个活动里面运行一个定时查状态的线程并且最终活动销毁也没有把线程关闭这种我认为是必须处理的特别是这个活动调用频繁的时候会有很多实例泄漏。2.3 未注销监听器、回调或广播本质是监听器、回调函数或者广播接收器持有某个对象context其被设置到一个长生命周期的对象里面导致被监听器、回调函数或者广播接收器持有context的对象不能释放这种和上面非静态内部类/匿名类的情况有点像。区别在于监听器、回调函数、广播接收器本身不会有执行结束的时候需要看其被设置到哪个对象此对象生命周期如何。比如活动里面某个按钮的点击事件我们一般就是new一个匿名对象塞进去这个按钮在活动结束后会回收所以也不会内存泄漏但是如果我们给一个网络请求设置回调函数那就得看活动结束时这个网络请求是不是还在了例如30s超时但是一直没收到响应那就要等到超时跑onFailure结束此网络请求完成回调函数才可以回收被持有this指针的外部对象才可以回收2.4 资源未释放例如数据库操作未关闭cursor文件打开未关闭等... 这些也可能导致内存泄漏发生三、如何处理内存泄漏这里要提到一点处理内存泄漏可能有多种方式但是必须先以原需求功能正常使用为前提不恰当的处理方式可能会引入比内存泄漏还严重的问题。3.1 正确使用context对象对于单例类持有context导致的内存泄漏这里建议的处理方法有几种。首先考虑的是查看能否可以换成applicationContext如果可以的话在接收外部传递的context时先调用getApplicationContext后再存储起来避免发生内存泄漏其次弱引用持有context这样子不会导致活动出现内存泄漏但是每次使用时需要判断context是否还可用。适用场景少不推荐还有就是在合适的时机解除对context的引用(例如在活动onDestroy取消引用onCreate添加引用)这种做法需要考虑是否有可能出现在使用时context时活动已经销毁的情况比较容易出现问题最后其实可以考虑不处理毕竟单例只实例化一次就算持有context导致活动泄漏也就一个不好处理的话可以当鸵鸟某种程度是可以接受的甚至可以将这些不好处理的需要长时间持有活动context的对象都在一开始弄好持有相同的活动context让这个活动一直存在。毕竟我们最终目的不是处理内存泄漏而是保持程序稳定运行。获取系统服务导致的内存泄漏这种情况处理方式就是优先使用applicationContext去获取系统服务ui相关的系统服务才使用活动的context去获取一般ui相关系统服务内部有做好处理即便 使用了活动的context也不会导致泄漏。全局的静态容器存储了对象但是未清理全局静态容器存储的对象持有context的情况处理方法一是在对象需要销毁时将其从容器移除这是比较好的做法方法二是根据需求可以选择定时清理或者其它机制清理。3.2 静态内部类弱引用此方法主要用于非静态内部类/匿名类默认持有外部引用导致的泄漏属于常见的处理方法但是有一些坑需要注意的! 首先举个例子原先我们new了一个CallBack并实现相应逻辑后塞到一个网络请求中只要网络请求没结束被callback持有context的活动也一直在callback处理响应没什么问题但是改成静态内部类弱引用后可能会有以下问题静态内部类MyCallBack里面弱引用了activity网络请求中的回调是MyCallBack的一个实例收到响应时activity已经回收了弱引用不加引用计数activity没有其他对象引用它就被回收如果不判空和判活动是否还存在直接调用get后使用可能会崩溃在判断到活动已销毁后回调的工作可能没法正常进行了这时候需要考虑能否符合原需求如果收到响应后的操作必须执行且依赖活动的内容那这种方法不适合你只能网络请求结束后再让活动被回收了。3.3 需要长时间运行的对象做好结束判断包括线程、异步任务、计时器等在内如果需要长时间运行那么在运行逻辑中建议加入判断活动是否结束以便可以正常回收对象进而避免活动内存泄漏。当然还是要看需求而定有些东西活动结束也还要继续跑自己判断是否有合理的回收时机即可或者泄漏可以接受也行。3.4 onDestroy该做的事情要做好其实很多内存泄漏可以在onDestroy处理同样的处理不好也会造成内存泄漏。这里列举一些onDestroy要回收/取消的东西1.对象被存储到全局的静态容器需要移除2.异步任务、计时器、线程是否需要结束或者取消3.监听器、广播、回调函数是否需要移除4.清理在活动中实例化的静态变量。四、如何排查java层内存泄漏这里推荐两个工具第一个是as自带的profiler使用方法已经有很多文章了我就不写了贴个链接自己看还是比较简单的。Android Studio自带Profiler工具内存泄露分析步骤_android studio 内存泄漏检测工具-CSDN博客Android Studio利用 Profiler 来监控 CPU、内存和网络 - 简书我们在内存监控里通过heap dump捕获堆转储确认哪些对象发生泄漏后就可以按照上文介绍的方法进行处理了。第二个可以使用LeakCanary使用方法网上也有教程贴个链接。Android内存泄露检测之LeakCanary的使用 - 简书这里说一下leakCanary对于那些在活动销毁时还引用它导致活动不能回收的对象会认为泄漏其中可能有一些只是销毁不及时但过段时间就回收的需要注意是否能满足你的需求。例如活动销毁但是有个回调函数持有引用在等待网络请求响应收到响应处理完后callback就能回收activity也可以随着回收这种情况leakCanary会认为泄漏。

相关新闻