美文网首页
lakecanary源码解析

lakecanary源码解析

作者: 刘佳阔 | 来源:发表于2020-04-27 10:15 被阅读0次

    title: lakecanary源码解析-图床版
    date: 2020-03-13 20:48:00
    tags: [android 工具源码]


    简单说.lakecanary 利用了java里的弱引用. 他把activity声明为弱引用.当一个引用对象只有弱引用时,会加入弱引用的ReferenceQueue中. lakecanary还有一个自己保存的所有要检测泄露的弱引用对象的队列. 两个队列想比较.都存在的对象.说名只有弱引用了.也就是可以被gc回收了.就排除掉. 同时通过开始HeapDumper(堆信息转储) 查看堆信息的引用链.找到内存泄露的点.

    基础知识

    内存泄漏(Memory Leak)

    是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

    就是我们已经使用完的对象.但是仍有不知名的强引用在指向他.因此gc认为他还有用不去回收,而我们认为他需要回收却没法回收.占据内存无法释放.

    可达性分析

    java通过可达性分析来判断对象是否需要被回收.该方法的基本思想是通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

    GC Roots

    Java语言中,可作为GC Roots的对象包含以下几种:

    1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。(方法的形参和方法内的局部变量)
    2. 方法区中静态属性引用的对象(staic 属性)
    3. 方法区中常量引用的对象(static final属性)
    4. 本地方法栈中(Native方法)引用的对象(可以理解为:引用Native方法的所有对象)

    WeakReference

    弱引用,用弱引用指向的对象,一旦这个对象没有任何强引用指向他了.他就可以被垃圾回收收走. 我们在代码中用弱引用来防止内存泄露

    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
    

    弱引用创建时,可以传入一个ReferenceQueue. 有什么用呢?就是当弱引用指向的对象.没有任何强引用的时候,这时称他为弱可达.也就是只有这个弱引用可以指向他.这时可以被垃圾回收.而被回收后,弱引用对象,会加入这个ReferenceQueue中. 这里我们知道.对象变成弱可达的时候,也就意味着他可以被gc回收不会泄露了.

    在对象被回收后,会把弱引用对象,也就是WeakReference对象或者其子类的对象,放入队列ReferenceQueue中,注意不是被弱引用的对象,被弱引用的对象已经被回收了。

    ReferenceQueue名义上是一个队列,但实际内部并非有实际的存储结构,它的存储是依赖于内部节点之间的关系来表达。可以理解为queue是一个类似于链表的结构,这里的节点其实就是reference本身。可以理解为queue为一个链表的容器,其自己仅存储当前的head节点,而后面的节点由每个reference节点自己通过next来保持即可。

    基础原理

    1. 监听activity状态.等发生ondestory时,表示该activity应该可以被回收了,通过注册 Application的ActivityLifecycleCallbacks来监听activity的onDestory事件.发生时,表示该activity应该被回收了
       application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new Application.ActivityLifecycleCallbacks() {
            @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            }
    
            @Override public void onActivityStarted(Activity activity) {
            }
    
            @Override public void onActivityResumed(Activity activity) {
            }
    
            @Override public void onActivityPaused(Activity activity) {
            }
    
            @Override public void onActivityStopped(Activity activity) {
            }
    
            @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
    
            @Override public void onActivityDestroyed(Activity activity) {
              ActivityRefWatcher.this.onActivityDestroyed(activity);
            }
          };
    
    1. 为这个activity建立弱引用,进行追踪

    watchedReference就是这个activity.为他生成一个唯一的key来标识对象.创建一个KeyedWeakReference,并传入ReferenceQueue . 当这个activity会变成弱可达的时候.就进入ReferenceQueue里了.retainedKeys 是用来保存所有待管程的对象的key,表示所有待回收的对象可以.如果gc后 retainedKeys 不为空,那么遗留的就代表发生了内存泄露

       private final Set<String> retainedKeys;
      private final ReferenceQueue<Object> queue;
    public void watch(Object watchedReference, String referenceName) {
      
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
    
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    
    1. 触发gc, 看到人家是查看源码发现Syste.gc并不能每次都实时触发,所以采用Runtime.gc(),触发后又让虚拟机执行对象的finalize方法,finalize方法只会执行一次,是对象gc前挽救自己的最后一个机会.
    GcTrigger DEFAULT = new GcTrigger() {
        @Override public void runGc() {
          // Code taken from AOSP FinalizationTest:
          // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
          // java/lang/ref/FinalizationTester.java
          // System.gc() does not garbage collect every time. Runtime.gc() is
          // more likely to perfom a gc.
          Runtime.getRuntime().gc();
          enqueueReferences();
          System.runFinalization();
        }
    
        private void enqueueReferences() {
          // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
          // references to the appropriate queues.
          try {
            Thread.sleep(100);
          } catch (InterruptedException e) {
            throw new AssertionError();
          }
        }
      };
    
    1. 看这个对象gc后是否还有变成弱可达,,如果没有变成弱可达,就说明有强引用对象指向他.就说明泄露了.
    
        //retainedKeys中持有所有要观察的对象的key,如果一个对象变成弱可达,因为我们之前采用弱引用进行了包装,那么他在就会在gc前出现在 ReferenceQuene 中,那么我们retainedKeys中对应的key就要删除.因为这个对象肯定可以被回收了.
        private final ReferenceQueue<Object> queue;
      private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
          retainedKeys.remove(ref.key);
        }
      }
      //判断对象是否泄漏,也就是这个弱引用对象的key在retainedKeys中有数据
      private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
      }
    
    1. 查找这个activity对应的对象的引用链.通过查看堆HeapDumper(堆信息转储)
    通过fileProvider暴露一个路径,在用debug生成堆信息转储文件.
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    
    因为这个分析过程比较费时.因此采用开启一个服务.并在单独的进程中启动.以防app进程造成影响.
    
     heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
                  
    

    别人写的新版.原理还都是一样的 https://www.jianshu.com/p/75f0e1fea3e0

    image-20200314230009507

    额外技能点

    Executor

    通过线程工程,创建一个已经配置好的线程池.

    Executors.newSingleThreadExecutor(new LeakCanarySingleThreadFactory(threadName));
    }
    

    https://www.cnblogs.com/MOBIN/p/5436482.html

    https://blog.csdn.net/coding_or_coded/article/details/6856014

    https://blog.csdn.net/weixin_40304387/article/details/80508236

    https://blog.csdn.net/tongdanping/article/details/79604637

    先记录吧.回头在看

    IdleHandler

    IdleHandler即在looper里面的message处理完了的时候去调用,

    向消息队列中添加一个新的MessageQueue.IdleHandler。当调用IdleHandler.queueIdle()返回false时,此MessageQueue.IdleHandler会自动的从消息队列中移除。或者调用removeIdleHandler(MessageQueue.IdleHandler)也可以从消息队列中移除MessageQueue.IdleHandler。

    这也是向handler里发消息的一种方式,但是这个消息会在消息队列的messagequene的消息都执行完成后在调用.

    每次消费掉一个有效message,在获取下一个message时,如果当前时刻没有需要消费的有效(需要立刻执行)的message,那么会执行IdleHandler一次,执行完成之后线程进入休眠状态,直到被唤醒

    简单说就是idleHander是上条消息被执行,而下条消息时间还没到的时候,会处理idleHandler来执行.他的添加和删除是线程安全的.每次消息队列的一次next,但是消息的时间还不够的时候,都会执行一次遍历idleHander集合,执行所有idle

       // This needs to be called from the main thread.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            postToBackgroundWithDelay(retryable, failedAttempts);
            return false;
          }
        });
    

    https://www.cnblogs.com/plokmju/p/handler_idleHandler.html

    https://wetest.qq.com/lab/view/352.html

    记一个名词

    单线程队列+异步线程

    相关文章

      网友评论

          本文标题:lakecanary源码解析

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