Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但有另外一个正在使用的对象持有它的引用,从而导致它不能回收停留在堆内存中,这就产生了内存泄漏。无用的对象占据着内存空间,使得实际可使用内存变小,形象地说法就是内存泄漏了。
内存泄漏是造成应用程序OOM的主要原因之一。Android系统为每个应用程序分配有限的内存,当应用中内存泄漏较多时,就难免会导致应用所需要的内存超出系统分配限额,从而导致OOM应用Crash;
1,单例造成:由于单例静态特性使得单例的生命周期和应用的生命周期一样长,如果一个对象(如Context)已经不使用了,而单例对象还持有对象的引用造成这个对象不能正常被回收; 2,非静态内部类创建静态实例造成:在Acitivity内存创建一个非静态内部类单例,避免每次启动资源重新创建。但是因为非静态内部类默认持有外部类(Activity)的引用,并且使用该类创建静态实例。造成该实例和应用生命周期一样长,导致静态实例持有引用的Activity和资源不能正常回收; 3,Handler造成:子线程执行网络任务,使用Handler处理子线程发送消息。由于handler对象是非静态匿名内部类的对象,持有外部类(Activity)的引用。在Handler-Message中Looper线程不断轮询处理消息,当Activity退出还有未处理或者正在处理的消息时,消息队列中的消息持有handler对象引用,handler又持有Activity,导致Activity的内存和资源不能及时回收; 4,线程造成:匿名内部类Runnalbe和AsyncTask对象执行异步任务,对当前Activity隐式引用。当Activity销毁之前,任务还没有执行完,将导致Activity的内存和资源不能及时回收; 5,资源未关闭造成的内存泄露:对于使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄露;
1,尽量使用Application的Context而不是Activity的 2,使用弱引用或者软引用 3,手动设置null,解除引用关系 4,将内部类设置为static,不隐式持有外部的实例 5,注册与反注册成对出现,在对象合适的生命周期进行反注册操作。 6,如果没有修改的权限,比如系统或者第三方SDK,可以使用反射进行解决持有关系
内存泄漏: 垃圾对象依旧占据内存,如水龙头的泄漏,水本来是属于水源的, 但是水龙头没关紧,那么泄漏到了水池;再来看内存,内存本来应 该被回收,但是依旧在内存堆中;总结一下就是内存存在于不该存在的地方(没用的地方)
内存溢出: 内存占用达到最大值,当需要分配内存时,已经没有内存可以分配了,就是溢出;依旧以水池为例, 水池的水如果满了,那么如果继 续需要从水龙头流水的话,水就会溢出。总结一下就是,内存的分配超出最大阀值,导致了一种异常
明白了两者的概念,那么两者有什么关系呢?
内存的溢出是内存分配达到了最大值,而内存泄漏是无用内存充斥了内存堆;因此内存泄漏是导致内存溢出的元凶之一,而且是很大的元凶;因为内存分配完后,哪怕占用再大,也会回收,而泄漏的内存则不然;当清理掉无用内存后,内存溢出的阀值也会相应降低。
LeakCanary是Square公司开源的一个检测内存的泄露的函数库,可以方便地和你的项目进行集成,在Debug版本中监控Activity、Fragment等的内存泄露; LeakCanary集成到项目中之后,在检测到内存泄露时,会发送消息到系统通知栏。点击后打开名称DisplayLeakActivity的页面,并显示泄露的跟踪信息,Logcat上面也会有对应的日志输出。同时如果跟踪信息不足以定位时,DisplayLeakActivity还为开发者默认保存了最近7个dump文件到App的目录中,可以使用MAT等工具对dump文件进行进一步的分析;
1,在android studio的build.gradle中引用leakcanary
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'2,自定义Application,并在onCreate方法中执行以下代码:
public class QAplication extends Application{ @Override public void onCreate() { super.onCreate(); ... ... //初始化LeakCanary if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); } }3,AndroidManifest.xml中添加权限
<!--SDCard中创建与删除文件权限--> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!--向SDCard写入数据权限--> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>已经结束了!通过以上配置,你就可以轻松使用LeakCanary检测内存泄漏了,当然这种简单的配置仅限于在Activity中进行检测,当然还存在其他类的内存泄漏,如Fragment等,这时我们就需要使用RefWatcher来进行监控了,具体见下面在非Activity的其它类中使用RefWatcher来进行内存泄漏的监控的讲解。
1.单例如下:
public class TestManager { //单例静态特性使得单例的生命周期和应用的生命周期一样长 private static TestManager instance; private Context context; /** * 传入的Context的生命周期很重要: * 如果传入的是Application的Context,则生命周期和单例生命周期一样长; * 如果传入的是Activity的Context,由于该Context和Activity的生命周期一样长,当Activity退出的时候它的内存不会被回收,因为单例对象持有它的引用; */ private TestManager(Context context) { this.context = context; } public static TestManager getInstance(Context context) { if (instance == null) { instance = new TestManager(context); } return instance; } }2,在activity中创建该单例对象:
public class LeakCanaryActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leakcanary); //获取单例对象,退出Activity即可模拟出内存泄露 TestManager testManager = TestManager.getInstance(this); } }运行App到LeakCanaryActivit页面并退出,在检测到内存泄露的时候,会发送消息到系统通知栏; 点击通知消息,打开名为DisplayLeakActivity的页面,并显示泄漏的跟踪信息;
第二节的例子代码只能够检测Activity的内存泄漏,当然还存在其他类的内存泄漏,这时我们就需要使用RefWatcher来进行监控。改写Application,如下所示。
public class LeakApplication extends Application { private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher= setupLeakCanary(); } private RefWatcher setupLeakCanary() { if (LeakCanary.isInAnalyzerProcess(this)) { return RefWatcher.DISABLED; } return LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { LeakApplication leakApplication = (LeakApplication) context.getApplicationContext(); return leakApplication.refWatcher; } }install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。 最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); LeakThread leakThread = new LeakThread(); leakThread.start(); } class LeakThread extends Thread { @Override public void run() { try { Thread.sleep(6 * 60 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override protected void onDestroy() { super.onDestroy(); RefWatcher refWatcher = LeakApplication.getRefWatcher(this); //1 refWatcher.watch(this); } }MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。 在注释1处得到RefWatcher,并调用它的watch方法,watch方法的参数就是要监控的对象。当然,在这个例子中onDestroy方法是多余的,因为LeakCanary在调用install方法时会启动一个ActivityRefWatcher类,它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露。这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。 运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来,比如三星S8的通知栏如下所示。 Notification中提示了MainActivity发生了内存泄漏, 泄漏的内存为787B。点击Notification就可以进入内存泄漏详细页,除此之外也可以通过Leaks应用的列表界面进入,列表界面如下图所示。 内存泄漏详细页如下图所示。 点击加号就可以查看具体类所在的包名称。整个详情就是一个引用链:MainActiviy的内部类LeakThread引用了LeakThread的this$0,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏。
mLastSrvView是InputMethodManager中的一个成员变量,通过反射我们可以得到该成员变量,将其设置为null就能避免掉内存泄露的风险。
这个mLastSrvView字段经过测试发现是华为手机特有的一个字段,google原生系统并没有这个字段。这个字段的作用是用来存储上一个页面view的,举个简单的例子ABC三个页面当你从C页面回退到B页面时,此时mlastsrvview保存这的就是C的decorview,当从B退回到A页面此时mlastsrvview保存的就是B的decorview。
所以想要发生内存泄露很简单,当从B回到A页面后停留一段时间leakcanary就会报出文章第二张图中的泄露问题,因为decorview一直被mlastsrvview引用着无法释放内存,知道原因后那么解决方法就很简单了,就是通过反射在页面销毁的时候通过反射将mlastsrvview设置为null就可以了。详情请查看博客:https://www.jianshu.com/p/95242060320f
1,工具类如下:
/** * 关于华为手机上mlastsrvview字段泄露引发的inputmethodmanager泄漏的处理工具类 * */ public class FixMemLeakUtils { private static Field field; private static boolean hasField = true; public static void fixLeak(Context context) { if (!hasField) { return; } InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); if (imm == null) { return; } String[] arr = new String[]{"mLastSrvView"}; for (String param : arr) { try { if (field == null) { field = imm.getClass().getDeclaredField(param); } if (field == null) { hasField = false; } if (field != null) { field.setAccessible(true); field.set(imm, null); } } catch (Throwable t) { t.printStackTrace(); } } } }然后在BaseActivity的onDestory方法中调用该工具类的fixLeak方法即可。