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

相关推荐
拭心10 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
fantasy_arch10 小时前
CPU性能优化-磁盘空间和解析时间
网络·性能优化
带电的小王12 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡12 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道13 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
码农老起13 小时前
企业如何通过TDSQL实现高效数据库迁移与性能优化
数据库·性能优化
java_heartLake13 小时前
Vue3之性能优化
javascript·vue.js·性能优化
arnold6613 小时前
探索 ElasticSearch:性能优化之道
大数据·elasticsearch·性能优化
阿甘知识库13 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道14 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频