APM框架Matrix源码分析(十四)ResourcePlugin之内存泄漏监控

Activity对象被生命周期更长的对象通过强引用持有,使Activity生命周期结束后仍无法被GC机制回收,导致其占用的内存空间无法得到释放。

ResourcePlugin就是用来监控Activity泄漏的,需要实现两大功能:

  1. Activity生命周期监控
  2. 查找泄漏对象

init

java 复制代码
@Override
public void init(Application app, PluginListener listener) {
    //创建ActivityRefWatcher
    mWatcher = new ActivityRefWatcher(app, this);
}

Activity内存泄漏检测委托给了ActivityRefWatcher去实现

start

java 复制代码
    @Override
    public void start() {
        //重置检测任务
        stopDetect();
        final Application app = mResourcePlugin.getApplication();
        if (app != null) {
            //监听Activity的生命周期
            app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);
            //子线程检测任务,每分钟检测一次
            scheduleDetectProcedure();
            MatrixLog.i(TAG, "watcher is started.");
        }
    }

registerActivityLifecycleCallbacks

通过registerActivityLifecycleCallbacks注册Activity生命周期监听onActivityDestroyed

java 复制代码
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new EmptyActivityLifecycleCallbacks() {

        @Override
        public void onActivityDestroyed(Activity activity) {
            //将销毁的Activity信息存入mDestroyedActivityInfos
            pushDestroyedActivityInfo(activity);
            //延迟2s触发gc
            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    triggerGc();
                }
            }, 2000);
        }
    };

在Activity执行onDestory时,将销毁的Activity包装成弱引用,存入mDestroyedActivityInfos,并唤醒任务继续执行。接着延迟2秒通过triggerGc向虚拟机申请垃圾回收。

java 复制代码
private void pushDestroyedActivityInfo(Activity activity) {
        //WeakReference包装activity对象,并封装为DestroyedActivityInfo
        final DestroyedActivityInfo destroyedActivityInfo
                = new DestroyedActivityInfo(key, activity, activityName);
        //存入mDestroyedActivityInfos
        mDestroyedActivityInfos.add(destroyedActivityInfo);
        synchronized (mDestroyedActivityInfos) {
            //通知'检测任务'有销毁Activity数据了,可以继续执行了,对应下面的mDestroyedActivityInfos.wait()
            mDestroyedActivityInfos.notifyAll();
        }
    }
scheduleDetectProcedure

scheduleDetectProcedure在子线程执行mScanDestroyedActivitiesTask,前台1分钟检测一次,后台20分钟检测一次。

java 复制代码
private final RetryableTask mScanDestroyedActivitiesTask = new RetryableTask() {

        @Override
        public Status execute() {
            //mDestroyedActivityInfos为空则阻塞,等待上面有销毁activity数据继续执行,对应上面mDestroyedActivityInfos.notifyAll()
            // If destroyed activity list is empty, just wait to save power.
            if (mDestroyedActivityInfos.isEmpty()) {
                MatrixLog.i(TAG, "DestroyedActivityInfo is empty! wait...");
                synchronized (mDestroyedActivityInfos) {
                    try {
                        while (mDestroyedActivityInfos.isEmpty()) {
                            //无数据阻塞,等待唤醒继续执行
                            mDestroyedActivityInfos.wait();
                        }
                    } catch (Throwable ignored) {
                        // Ignored.
                    }
                }
               //上层handler封装,会继续执行
                return Status.RETRY;
            }
            //申请gc
            triggerGc();

            final Iterator<DestroyedActivityInfo> infoIt = mDestroyedActivityInfos.iterator();

            while (infoIt.hasNext()) {
                final DestroyedActivityInfo destroyedActivityInfo = infoIt.next();
                //再次申请gc
                triggerGc();
                //gc后被回收,则移除activity对象
                if (destroyedActivityInfo.mActivityRef.get() == null) {
                    // The activity was recycled by a gc triggered outside.
                    MatrixLog.v(TAG, "activity with key [%s] was already recycled.", destroyedActivityInfo.mKey);
                    infoIt.remove();
                    continue;
                }
                //引用存在,检测此时加1
                ++destroyedActivityInfo.mDetectedCount;
                //检测次数没达到设置的阈值,继续gc,重复上面逻辑
                if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes
                        && !mResourcePlugin.getConfig().getDetectDebugger()) {
                    // Although the sentinel tell us the activity should have been recycled,
                    // system may still ignore it, so try again until we reach max retry times.
                    MatrixLog.i(TAG, "activity with key [%s] should be recycled but actually still exists in %s times, wait for next detection to confirm.",
                            destroyedActivityInfo.mKey, destroyedActivityInfo.mDetectedCount);

                    triggerGc();
                    continue;
                }
                //超过最大检测次数,对象没被回收,说明发生了泄漏
                if (mLeakProcessor.process(destroyedActivityInfo)) {
                    MatrixLog.i(TAG, "the leaked activity [%s] with key [%s] has been processed. stop polling", destroyedActivityInfo.mActivityName, destroyedActivityInfo.mKey);
                    infoIt.remove();
                }
            }

            return Status.RETRY;
        }
    };

通过mDestroyedActivityInfos.wait()实现线程阻塞,当队列不为空时唤醒任务进行Activity泄漏检测,通过弱引用对象是否为空,来判断对象回收情况,进而确定泄漏。

根据DumpMode创建不同的泄漏处理器,以AUTO_DUMP(Java层生成dump,子进程对hprof裁剪并上报)为例简单分析:

AutoDumpProcessor

java 复制代码
public class AutoDumpProcessor extends BaseLeakProcessor {

    private static final String TAG = "Matrix.LeakProcessor.AutoDump";

    public AutoDumpProcessor(ActivityRefWatcher watcher) {
        super(watcher);
    }

    @Override
    public boolean process(DestroyedActivityInfo destroyedActivityInfo) {
        //调用系统api  Debug.dumpHprofData dump出一个hprof文件
        final File hprofFile = getHeapDumper().dumpHeap(true);
        if (hprofFile != null) {
           //标记一下避免重复处理
           getWatcher().markPublished(destroyedActivityInfo.mActivityName);
            getWatcher().triggerGc();
            final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);
            //处理dump出来的hprof文件
            getHeapDumpHandler().process(heapDump);
        } else {
            MatrixLog.i(TAG, "heap dump for further analyzing activity with key [%s] was failed, just ignore.",
                    destroyedActivityInfo.mKey);
        }
        return true;
    }
}

getHeapDumpHandler().process(heapDump)会走到CanaryWorkerService(一个独立进程的Service)的onHandleWork:

java 复制代码
    @Override
    protected void onHandleWork(Intent intent) {
          //对hprof进行裁剪
         doShrinkHprofAndReport(heapDump);
         //上报
         CanaryResultService.reportHprofResult(this, zipResFile.getAbsolutePath(), heapDump.getActivityName());
    }

doShrinkHprofAndReport又调用了shrink

java 复制代码
public void shrink(File hprofIn, File hprofOut) throws IOException {
    FileInputStream is = null;
    OutputStream os = null;
    try {
        is = new FileInputStream(hprofIn);
        os = new BufferedOutputStream(new FileOutputStream(hprofOut));
        final HprofReader reader = new HprofReader(new BufferedInputStream(is));
        //1、收集Bitmap和String信息
        reader.accept(new HprofInfoCollectVisitor());
        // Reset.
        is.getChannel().position(0);
        //2、找到Bitmap、String中持有的byte数组,并找到内容重复的Bitmap
        reader.accept(new HprofKeptBufferCollectVisitor());
        // Reset.
        is.getChannel().position(0);
        //3、裁剪掉内容重复的Bitmap,和其他byte数组
        reader.accept(new HprofBufferShrinkVisitor(new HprofWriter(os)));
    } finally {
        if (os != null) {
            try {
                os.close();
            } catch (Throwable thr) {
                // Ignored.
            }
        }
        if (is != null) {
            try {
                is.close();
            } catch (Throwable thr) {
                // Ignored.
            }
        }
    }
}

Hprof文件中buffer区存放了所有对象的数据,包括字符串数据、所有的数组等,而我们的分析过程却只需要用到部分字符串数据和Bitmap的buffer数组,其余的buffer数据都可以直接剔除,这样处理之后的Hprof文件通常能比原始文件小1/10以上。

ResourcePlugin还提供了用于处理Activity泄漏的ActivityLeakFixer,这里就不分析了...

小结:

LeakCanary原理一样:

当jvm进行垃圾回收时,无论内存是否充足,如果该对象只有弱引用存在,那么就会被垃圾回收器回收,同时该引用会被加入到关联的ReferenceQueue。

利用弱引用的特性,获取当前引用,构建弱引用对象KeyedWeakReference并关联一个ReferenceQueue,保存到集合中。GC后,通过key删除已经回收的对象,剩下的对象存在泄漏嫌疑。

相关推荐
阿巴斯甜8 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker9 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952710 小时前
Andorid Google 登录接入文档
android
黄林晴11 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android