美文网首页
Android内存泄漏问题

Android内存泄漏问题

作者: 不会敲代码的好代码 | 来源:发表于2019-01-08 17:11 被阅读10次

    一、垃圾回收

    一般来说,程序使用内存遵循先向操作系统申请一块内存,使用内存,使用完毕之后释放内存归还给操作系统。然而在传统的C/C++等要求显式释放内存的编程语言中,在合适的时候释放内存是一个很有难度的工作,因此Java等编程语言都提供了基于垃圾回收算法的内存管理机制。

    常见的垃圾回收算法有引用计数算法(Reference Counting)、标注—清除算法(Mark and Sweep GC)、复制算法(Copying GC)和逐代回收(Generational GC)等算法,其中Android虚拟机采用的是标注—清除算法,并不是大多数JVM实现里采用的逐代回收算法。

    二、几个基本概念

    • 内存泄露:程序在向系统申请分配内存空间后(new),在使用完毕后未释放,结果导致不再使用的对象一直占据该内存单元,或者他们占用的内存没有及时得到释放,从而造成内存空间不断减少的现象。由于Android程序可以使用的内存较少,发生内存泄漏会导致内存更加紧张,甚至最终由于内存耗尽而发生OOM(out of memeroy)错误,导致应用崩溃。

    • 内存溢出:程序向系统申请的内存空间超出了系统能给的。比如内存只能分配一个int类型,我却要塞给他一个long类型,系统就出现oom。

    • 强引用:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。 当内存空间不足,Java虚拟机宁愿抛出OOM(OutOfMemoryError)错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

    • 软引用:如果一个对象只具有软引用,但内存空间足够时,垃圾回收器就不会回收它;直到虚拟机报告内存不够时才会回收, 只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    • 弱引用:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。 不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    • 虚引用:虚引用可以理解为虚设的引用,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。 虚引用主要用来跟踪对象被垃圾回收器回收的活动。 虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。 如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    三、Android内存泄漏存在的原因

    1.静态变量导致的内存泄漏
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
        
        private static View mView;
        
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate (savedInstanceState);
            setContentView (R.layout.activity_main);
            mView = new View (this);
        }
    }
    

    上面这段代码就会产生内存泄漏,mView是静态变量,它的内部持有当前的activity,因此Activity仍然无法释放。

    2.单例模式导致的内存泄漏

    不合理的单例模式也会引起内存泄漏,

    
    public class LeakTest {
    
        private static LeakTest mInstance;
    
        private LeakTest(Context context) {
        }
    
        /**
         * 单例模式
         */
        public static LeakTest getInstance(Context context) {
            if(mInstance == null) {
                mInstance = new LeakTest (context);
            }
            return mInstance;
        }
    
    }
    

    比如以上代码是一个提供单例的测试类,需要传入一个Contex进行获取,在Activity中使用的时候如果传入了改Activity的Context,当Activity不再使用的时候,如果在onDestroy时不进行操作,就会造成静态的单例变量一直引用这个Context,导致本该释放内存的变量一直占用内存造成内存泄漏。要解决这种类型的内存泄漏可以在引用Activity的Context时变成引用Application的Context。

    3.属性动画导致内存泄漏

    属性动画中有一类无限循环的动画,如果在Activity中播放此类动画但是没有在onDestroy中去停止动画,那么动画就会一直播放,尽管在当前界面以及看不到动画了。这个时候Activity的View会被动画持有,View又持有这个Activity,最终导致Activity无法释放,造成内存泄漏。解决方法是在onDestroy的时候调用animator.cancle()方法来停止动画。

    4.其他原因导致的内存泄漏
    • 资源对象没关闭造成的内存泄漏,如查询数据库后没有关闭游标cursor
    • 构造Adapter时,没有使用 convertView 重用
    • Bitmap对象不再使用时,没有调用recycle()释放内存
    • 对象被生命周期长的对象引用,如activity被静态变量引用导致activity不能释放,因为静态类生命周期比Activity长。
    • 想暂时规避内存泄漏问题,可以在manifest.xml加入:
    android:largeHeap="true"
    

    此时heapsize会增大2-3倍,缓解OOM的发生
    其他内存泄漏的原因参考:https://blog.csdn.net/cyq1028/article/details/19980369

    四、内存泄漏的排查

    1.LeakCanary

    A memory leak detection library for Android and Java.

    LeakCanary的工作机制:

    1. RefWatcher.watch() 创建一个 KeyedWeakReference 到要被监控的对象。

    2. 然后在后台线程检查引用是否被清除,如果没有,调用GC。

    3. 如果引用还是未被清除,把 heap 内存 dump 到 APP 对应的文件系统中的一个 .hprof 文件中。

    4. 在另外一个进程中的 HeapAnalyzerService 有一个 HeapAnalyzer 使用HAHA 解析这个文件。

    5. 得益于唯一的 reference key, HeapAnalyzer 找到 KeyedWeakReference,定位内存泄露。

    6. HeapAnalyzer 计算 到 GC roots 的最短强引用路径,并确定是否是泄露。如果是的话,建立导致泄露的引用链。

    7. 引用链传递到 APP 进程中的 DisplayLeakService, 并以通知的形式展示出来。

    LeakCanary的使用

    1.添加依赖

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

    2.新建一个Application类,用于使用LeakCanary

    public class ExampleApplication extends Application {
    
     public static RefWatcher getRefWatcher(Context context) {
        ExampleApplication application = (ExampleApplication) context.getApplicationContext();
        return application.refWatcher;
      }
    
      private RefWatcher refWatcher;
    
      @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.install() 会返回一个预定义的 RefWatcher,同时也会启用一个 ActivityRefWatcher,用于自动监控调用 Activity.onDestroy() 之后泄露的 activity,LeakCanary自动监控Activity,如果要在Fragment中使用LeakCanary需要在onDestroy方法中进行监控。

    public abstract class BaseFragment extends Fragment {
    
      @Override public void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
        refWatcher.watch(this);
      }
    }
    

    只要继承自基类的Fragment都会被监控内存泄漏的情况。

    3.LeakCanary的自定义和其他操作参考
    https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/

    内存泄漏 泄漏详情及原因

    2. Android Profiler

    相关文章

      网友评论

          本文标题:Android内存泄漏问题

          本文链接:https://www.haomeiwen.com/subject/pfdurqtx.html