一、内存溢出原因 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。 memory leak会最终会导致out of memory! 内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。 内存泄漏的根部原因在于存在无效引用导致无用对象不能被及时回收。 1、内存溢出原因 1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 3.代码中存在死循环或循环产生过多重复的对象实体; 4.使用的第三方软件中的BUG; 5.启动参数内存值设定的过小 2、内存溢出解决方案 1. 修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。) 2.检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。 3.对代码进行走查和分析,找出可能发生内存溢出的位置。 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。 检查代码中是否有死循环或递归调用。 检查是否有大循环重复产生新对象实体。 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。 4. 使用内存查看工具动态查看内存使用情况,后续章节中介绍。 3、常见内存泄漏 Android (JAVA) 的内存分配策略有三种: 静态存储区:存放静态数据和常量,与应用生命周期保持一致,编译阶段,固定内存 栈区:存放方法体中的临时变量,方法执行结束后被回收 堆区:存放对象,其生命周期由GC控制,动态分配内存,由于不规范的编码方式,影响到了GC行为,导致内存泄露。
1、持有对象不释放,例如在Activity中注册了一个监听,但是在onDestroy()中却未进行反注册,导致这个监听一直存在,由于引用的关系对象不能被回收。 2、资源未关闭造成的内存泄露,BroadcastReceiver/ ContentObserver /File /Cursor/ Stream /Bitmap/ Handler,需要成对创建或者销毁,例如在Activity/Fragment.onCreate() 注册,在Activity/Fragment.onDestroy() 去注册。 3、非静态内部类/匿名内部类默认会持有外部类的引用,如果将这个引用再传入一个异步线程,并被异步线程持有,此线程和此Activity生命周期不一致的时候,则会导致内存泄露,延迟的Handler事件处理也要特别注意,一旦延迟的时间超过了Activity的销毁时间,就会造成内存泄露。 以Handler为例,延迟执行任务的Message将会持续存在于主线程中,它持有该Activity的Handler引用,然后又因为Handler为匿名内部类,它会持有外部类Activity的引用,所以此时Finish()掉的Activity就不会被GC回收掉,而Activity出现内存泄露代价是昂贵的,因为它里面包含了UI等所有的视图层级,这占用了很多的空间。 一种解决方案是将内部类声明为静态的,Context声明为弱引用,则其存活期跟Activity的生命周期无关,但这样一来临时量就变为了持久量,不予推荐。第二种方案是在Activity的onDestry()中及时终止任务,取消线程,清空消息。 二、内存泄漏分析 MAT + LeakCanary + Monkey Test http://www.eclipse.org/mat/ https://github.com/square/leakcanary 1、MAT下载安装(作为Eclipse插件安装) 地址:https://www.eclipse.org/mat/ 将下载工具解压至Eclipse的dropins,在dropins下创建mat.link文件,内容为 path=D:\\eclipse\\dropins\\+“解压文件路径” 如:path=D:\\eclipse\\dropins\\MemoryAnalyzer-1.8.1.20180910-win32.win32.x86_64 完成后可在Eclipse中看到MAT工具。
2、获取hprof文件 DDMS 可以将当前的内存 Dump成一个 hprof格式的文件,MAT 读取这个文件后会给出方便阅读的信息,配合它的查找,对比功能,就可以定位内存泄漏的原因。 点击工具栏上的 Dump HPROF file按钮,将内存信息保存成文件。如果是未集成MAT Eclipse 插件,则选择hprof文件的保存路径,默认以packageName.hprof命名。DDMS Dump 出的文件要经过转换才能被 MAT识别,Android SDK提供了这个工具 hprof-conv (位于 sdk/tools下),from the Dalvik format to the J2SE HPROF format。
执行命令:hprof-conv old.hprof new.hprof
如果是用 MAT Eclipse 插件获取的 Dump文件,则不需要经过转换,Adt会自动进行转换然后打开。 3、MAT分析内存堆 打开后的hprof文件 点击 Actions下的 Histogram项将得到 Histogram结果 它按类名将所有的实例对象列出来 在某一项上右键打开菜单选择 list objects ->with incoming refs 将列出该类的实例,它展示了对象间的引用关系 快速找出某个实例没被释放的原因,可以右健 Path to GC Roots-->exclue all phantom/weak/soft etc. reference ,用这个方法可以快速找到某个对象的 GC Root,一个存在 GC Root的对象是不会被 GC回收掉的。
三、概念 Shallow heap:某个对象所消耗掉的内存。Retained set of X: 当X垃圾回收进行时,一系列将被GC删除的对象集合。Retained heap of X:X保持的活跃内存。一个对象的Shallow heap是它在heap中的大小; Retained heap指的是当这个对象进行垃圾回收的时候,heap中可以被释放掉的内存大小。 Reference对象:
Strong reference | 从不被GC回收的 Object o=new Object(); Object o1=o; | Soft reference | 正常情况下不被GC回收,只在具有明显内存操作要求情况下被回收。 Memory-sensitive caches | Weak reference | 可被GC回收。Weak reference是一种不够强大的reference,以至于它不能使对象强行留在内存中。如果一个对象只是被Weak referenced,意味着它随时可能被GC回收。 |
四、内存溢出分析参考资料 https://android-developers.googleblog.com/2011/03/memory-analysis-for-android.html http://kohlerm.blogspot.com/2009/07/eclipse-memory-analyzer-10-useful.html http://www.cnblogs.com/0616--ataozhijia/p/3954423.html https://android-developers.googleblog.com/2009/01/avoiding-memory-leaks.html |