Android开发Android开发经验谈Android技术知识

Android内存泄漏定位、分析、解决全方案

2019-02-11  本文已影响16人  TryEnough

原文链接

更多教程


为什么会发生内存泄漏


内存空间使用完毕之后未回收, 会导致内存泄漏。有人会问:Java不是有垃圾自动回收机制么?不幸的是,在Java中仍存在很多容易导致内存泄漏的逻辑(logical leak)。虽然垃圾回收器会帮我们干掉大部分无用的内存空间,但是对于还保持着引用,但逻辑上已经不会再用到的对象,垃圾回收器不会回收它们。

例如

Java判断无效对象的原理

Android内存回收管理策略图:


图中的每个圆节点代表对象的内存资源,箭头代表可达路径。当圆节点与 GC Roots 存在可达路径时,表示当前资源正被引用,虚拟机是无法对其进行回收的(如图中的黄色节点)。反过来,如果圆节点与 GC Roots 不存在可达路径,则意味着这块对象的内存资源不再被程序引用,系统虚拟机可以在 GC 过程中将其回收掉。

从定义上讲,Android(Java)平台的内存泄漏是指没有用的对象资源任与GC-Root保持可达路径,导致系统无法进行回收。

内存泄漏带来的危害

Android中常见的可能发生内存泄漏的地方


1.在Android开发中,最容易引发的内存泄漏问题的是Context

比如ActivityContext,就包含大量的内存引用,一旦泄漏了Context,也意味泄漏它指向的所有对象。

造成Activity泄漏的常见原因:

static Activity activity; //这种代码要避免
public class Singleton {
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context context){
        this.mContext = context;
    }

    public static Singleton getInstance(Context context){
        if (instance == null){
            synchronized (Singleton.class){
                if (instance == null){
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

在调用Singleton的getInstance()方法时传入了Activity。那么当instance没有释放时,这个Activity会一直存在。因此造成内存泄露。
解决方法:

可以将new Singleton(context)改为new Singleton(context.getApplicationContext())即可,这样便和传入的Activity没关系了。

解决方法:

1.将内部类变成静态内部类;
2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用;
3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务;

例如:
发生内存泄漏的代码:

public class LeakAct extends Activity {  
     @Override
     protected void onCreate(Bundle savedInstanceState) {    
         super.onCreate(savedInstanceState);
         setContentView(R.layout.aty_leak);
         test();
     } 
     //这儿发生泄漏    
     public void test() {    
         new Thread(new Runnable() {      
             @Override
             public void run() {        
                 while (true) {          
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         }).start();
     }
 }

解决方法:

public class LeakAct extends Activity {  
     @Override
     protected void onCreate(Bundle savedInstanceState) {    
         super.onCreate(savedInstanceState);
         setContentView(R.layout.aty_leak);
         test();
     }  
     //加上static,变成静态匿名内部类
     public static void test() {    
         new Thread(new Runnable() {     
             @Override
             public void run() {        
                 while (true) {          
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             }
         }).start();
     }
 }

原文链接

更多教程


1.可以把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露;
2.如果想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的.
3.在界面销毁是,释放handler资源

@Override
 protected void doOnDestroy() {        
     super.doOnDestroy();        
     if (mHandler != null) {
         mHandler.removeCallbacksAndMessages(null);
     }
     mHandler = null;
     mRenderCallback = null;
 }

线程产生内存泄露的主要原因在于线程生命周期的不可控。如果我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

要解决Activity的长期持有造成的内存泄漏,可以通过以下方法:

2.Bitmap没调用recycle()

Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才设置为null.

3.集合中对象没清理造成的内存泄露

我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。
解决方案:

在Activity退出之前,将集合里的东西clear,然后置为null,再退出程序。

4.注册没取消造成的内存泄露

这种Android的内存泄露比纯Java的内存泄漏还要严重,因为其他一些Android程序可能引用系统的Android程序的对象(比如注册机制)。即使Android程序已经结束了,但是别的应用程序仍然还有对Android程序的某个对象的引用,泄漏的内存依然不能被垃圾回收。
解决方案:

1.使用ApplicationContext代替ActivityContext;
2.在Activity执行onDestory时,调用反注册;

5.资源对象没关闭造成的内存泄露

资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。而不是等待GC来处理。

6.占用内存较多的对象(图片过大)造成内存溢出

因为Bitmap占用的内存实在是太多了,特别是分辨率大的图片,如果要显示多张那问题就更显著了。Android分配给Bitmap的大小只有8M.
解决方法:

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一 
//软引用
 SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap); 
//回收操作
 if(bitmap != null) {  
       if(bitmap.get() != null && !bitmap.get().isRecycled()){ 
           bitmap.get().recycle(); 
           bitmap = null;  
       } 
} 

7.WebView内存泄露(影响较大)

解决方案:

用新的进程起含有WebView的Activity,并且在该Activity 的onDestory() 最后加上 System.exit(0); 杀死当前进程。

检测内存泄漏的方法


Lint 是 Android Studio 自带的工具,使用姿势很简单 Analyze -> Inspect Code 然后选择想要扫面的区域即可

对可能引起泄漏的编码,Lint 都会进行温馨提示:

Square 公司出品的内存分析工具,官方地址如下:https://github.com/square/leakcanary/
LeakCanary 需要在项目代码中集成,不过代码也非常简单,如下的官方示例:

在你的 build.gradle:

dependencies {
 debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
 releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
 // Optional, if you use support library fragments:
 debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}

在 Application 类:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

当内存泄漏发生时,LeakCanary 会弹窗提示并生成对应的堆存储信息记录

-3.Android Monitor

开Android Studio,编译代码,在模拟器或者真机上运行App,然后点击

,在Android Monitor下点击Monitor对应的Tab,进入如下界面

在Memory一栏中,可以观察不同时间App内存的动态使用情况,点击 可以手动触发GC,点击

可以进入HPROF Viewer界面,查看Java的Heap,如下图


Reference Tree代表指向该实例的引用,可以从这里面查看内存泄漏的原因,Shallow Size指的是该对象本身占用内存的大小,Retained Size代表该对象被释放后,垃圾回收器能回收的内存总和。

扩展知识


四种引用类型的介绍


原文链接

更多教程


上一篇下一篇

猜你喜欢

热点阅读