Activity对象被生命周期更长的对象通过强引用持有,使Activity生命周期结束后仍无法被GC机制回收,导致其占用的内存空间无法得到释放。
ResourcePlugin就是用来监控Activity泄漏的,需要实现两大功能:
- Activity生命周期监控
- 查找泄漏对象
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删除已经回收的对象,剩下的对象存在泄漏嫌疑。