内存泄露原因
由于某些资源应该被释放却没有被释放,一直被某些对象占用,导致GC不能正常回收资源。
JVM内存分配策略
java运行时的内存分配策略有:静态分配、栈式分配、堆式分配。
不同的分配策略对应不同的内存区:
静态内存区:主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
栈区:当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
堆区:又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收。
局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体)—— 因为它们属于类,类对象终究是要被new出来使用的。
Java的内存管理
Java的内存管理,就是内存的分配与释放问题。
分配是程序中通过new等关键字获取的,释放是GC做的。JVM需要维护一套所有对象的引用关系,所以JVM的运行速度会有些慢。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。
常见的泄露类型
1、集合类泄露:只add,没有进行remove
2、单例:与应用生命周期同样长,使用不当会造成泄露。单例时传入的context应该是applicationcontext,如果是activitycontext,会造成内存泄露。
3、非静态内部类:静态内部类会持有外部类的引用。应该将非静态内部类抽出来。
4、匿名内部类 :被异步线程持有。
5、Handler 造成的内存泄漏:很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写一不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。
由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。
6、尽量避免使用 static 成员变量
综述,即推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。
前面提到了 WeakReference,所以这里就简单的说一下 Java 对象的几种引用类型。
Java对引用的分类有 Strong reference, SoftReference, WeakReference, PhatomReference 四种
7、尽量避免使用 static 成员变量
以上,参考https://blog.csdn.net/u013495603/article/details/50696170
检测工具
- 功能强大PC端检测工具,如
MemoryAnalyzer
运行在PC端抓取Android手机中的dump文件进行深度分析。 http://www.eclipse.org/mat/ - 小而优的Android端检测工具,如
LeakCanary
随App一起安装会在Android手机桌面安装的内存泄露检测App https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
编程注意点:
- 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用
getApplicationContext 或者 getApplication,以避免 Activity
被外部长生命周期的对象引用而泄露。 - 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context
),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop
或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap
后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ;
array = null)等,最好遵循谁创建谁释放的原则。正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标
Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。