一、什么是内存泄漏?
内存泄漏是指在程序运行过程中,分配给程序的内存空间在不需要时没有被正确释放或回收的情况。
二、Java层内存泄漏。
- Android应用程序中Java层常见的内存泄漏情况:
- Context引用泄漏:Android中的Context是一个常见的泄漏源。例如,在生命周期长于Context的类中持有Context引用,导致Context无法被垃圾回收。
- 非静态内部类和匿名内部类:非静态内部类和匿名内部类会隐式持有外部类的引用,如果它们的生命周期比外部类更长,就会导致外部类无法被垃圾回收。
- Handler引起的泄漏:使用Handler时,如果Handler持有Activity或Fragment的引用,并在消息队列中排队等待处理,那么这些Activity或Fragment无法被垃圾回收。
- 单例模式:不正确的使用单例模式可能导致对象长时间存活在内存中,造成内存泄漏。确保单例对象在不需要时能够释放相关引用。
- 监听器和回调:如果注册的监听器和回调没有及时取消注册,这些监听器和回调持有的引用可能阻止相关对象的垃圾回收。
- 静态引用导致的泄漏:使用静态引用可能导致对象在应用程序的整个生命周期内持续存在,即使不需要也不会释放,从而导致内存泄漏。
- 长时间的后台任务:在Android应用中进行长时间的后台任务,如果不正确的处理,可能会导致相关资源无法释放,从而引发内存泄漏。
- 未关闭的资源:Bitmap资源、文件句柄、数据库连接、网络连接等未关闭也可能导致内存泄漏。
三、Native层内存泄漏。
- Android应用程序中Native层常见的内存泄漏情况:
- 内存分配未释放:在C/C++代码中使用malloc、new等函数分配内存,但未使用free、delete等函数释放,导致内存泄漏。
- 未释放的本地引用:在JNI中,如果分配了本地引用(Local Reference)但未正确释放,会导致内存泄漏。
- 未释放的全局引用:在JNI中,如果分配了全局引用(Global Reference)但未正确释放,会导致内存泄漏。
- 资源未释放:打开文件、网络连接、数据库连接等资源,但在不在需要时未正确关闭,导致资源泄漏。
- 未释放的线程和锁:在本地代码中创建了线程、锁等资源,但在不在需要时未正确释放,可能导致线程或锁资源泄漏。
- 循环引用:在C++等编程中,对象之间可能存在循环引用,使引用计数无法降为零,从而导致内存泄漏。
- 异常处理不当:在本地代码中发生异常时,如果未正确释放已分配的内存和资源,会导致内存无法释放。
四、内存泄漏如何检测。
进行内存泄漏检测建议使用Android Profiler的内存分析器,它能够显示内存使用的实时图表,并且可以捕获内存快照、强制执行垃圾回收以及动态跟踪内存分配和回收。
- Capture heap dump:获取当前内存快照。
- Record native allocations:记录一段时间内C/C++内存分配和回收。
- Record Java/kolin allocation:记录一段时间内Java/Kotlin对象的分配和回收。
- 内存使用详情:显示应用当前的内存使用详情。
- 强制执行垃圾回收。
- Java内存泄漏检测
Java内存泄漏检测分两种场景:
1、严重泄漏,内存增加速度快。
对于严重的内存泄漏,内存泄漏对象的数量会明显增加,快速占据程序内存的大部分空间,对于这种泄漏场景,通过Record Java/kolin allocation功能,记录该时间段内Java对象的分配和回收。其中数量大而且占用内存远超其它对象的对象即为内存泄漏的对象。
标号1显示了对象的分配详情(数量、内存),通过这些数据,我们能够大致判断出来泄漏的对象,然后通过编号2,根据对象创建的堆栈信息(如果没有堆栈信息,则该对象在JNI中创建),定位到具体的对象。
2、轻微泄漏,内存缓慢增加。
对于轻微的内存泄漏,需要经过长时间的运行,让泄漏的内存占据程序的大部分空间,再通过Capture heap dump功能,获取当前的内存快照。
然后通过标号1查看数量大、内存占用大的对象,通过标号2进一步分析该对象引用情况,查找不被释放的原因,最后定位到问题。
- Native内存泄漏
在C/C++中,内存管理相对更加手动,没有内置的机制来直接捕获内存状态,因此没法实现内存快照功能,但是通过Android Profiler的内存分析器,通过Record native allocations功能,可以动态跟踪C/C++内存分配和回收,从而实现native内存泄漏检测。
在Native内存泄漏检测过程中,不管是严重泄漏,还是轻微泄漏,都需要让泄漏的内存占据整个程序内存的大部分空间,这样方便我们检测到内存泄漏的位置。
标号1应该选择Array by callstack,便于我们查看调用信息,标号2 Module Name,进行分析时,应该选择应用中明确使用的so库,标号3是函数的编码,通过这些信息,可以定位到具体函数,最后找出泄漏的原因。