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删除已经回收的对象,剩下的对象存在泄漏嫌疑。

相关推荐
Kevin Coding2 小时前
Flutter使用Flavor实现切换环境和多渠道打包
android·flutter·ios
yashunan3 小时前
Web_php_unserialize
android·前端·php
taopi20244 小时前
android java系统弹窗的基础模板
android·java·开发语言
志尊宝4 小时前
深入探索 Android 技术:从基础到前沿
android
字节全栈_BjO6 小时前
mysql死锁排查_mysql 死锁问题排查
android·数据库·mysql
桦说编程19 小时前
CompletableFuture 超时功能有大坑!使用不当直接生产事故!
java·性能优化·函数式编程·并发编程
恋猫de小郭20 小时前
Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年
android·ide·android studio
aaaweiaaaaaa1 天前
php的使用及 phpstorm环境部署
android·web安全·网络安全·php·storm
工程师老罗1 天前
Android记事本App设计开发项目实战教程2025最新版Android Studio
android
pengyu1 天前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart