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

相关推荐
千里马学框架5 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台5 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone5 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc5 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo5 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077005 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
liangshanbo12155 天前
React 性能优化实战
性能优化·react
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas