KOOM线上APM监控最全剖析
LeakCanary源码分析: 一文让你彻底理解LeakCanary的工作原理
private final Set retainedKeys;
private final ReferenceQueue
如何判定是否发生泄漏,主要源码解析:
RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// 支线代码: watchExecutor的实现是AndroidWatchExecutor
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
// 检测Activity是否被回收
return ensureGone(reference, watchStartNanoTime);
}
});
}
// 核心代码
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
// 计算时间差提示给开发
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 尝试移除已经被回收的Activity对应的key(因为代码跑到这里可能已经gc过了)
removeWeaklyReachableReferences();
// 检测Activity是否已经被回收(key被移除了就是被回收了)
if (gone(reference)) {
return DONE;
}
// 如果没有被回收,尝试进行一次gc(这就是我们上面说的必要的时候,后面有细讲)
gcTrigger.runGc();
// gc之后再进行一次移除
removeWeaklyReachableReferences();
// 如果Activity还没有被回收,说明发生了泄漏
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 抓取堆信息并生成文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 对泄漏结果进行分析并通知给相应的服务,然后就会弹出一个通知告诉我们发生了泄漏
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
// 检测Activity是否已经被回收,只要Activity对应的key不在了,就说明已经回收了
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
// 移除所有已经被回收的对象,被回收了就移除activity对应的key
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
// 遍历引用队列,同时移除该弱引用指向的Activity的key
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
可以看到,首先,我们将检测的代码逻辑丢到watchExecutor来执行(watchExecutor其实是个AndroidWatchExecutor,用来切换线程),当我们的检测逻辑运行时,大概率已经发生过gc了(这是watchExecutor的功劳),所以我们尝试去清除一次activity的key队列,然后检测被destroy的activity是否已经被回收,如果没有被回收,也不一定发生了泄漏,因为可能还没有进行过gc,所以我们手动进行了一次gc,然后再次检测该activity 对应的key是否还在key队列,如果还在,那么就说明发生了泄漏,就直接dump堆空间以及相关信息,并提示给开发者。
还记得我们前面为Activity生成的key吗,当这个Activity被回收后,指向它的弱引用就会被放入引用队列queue中,所以当我们检测到queue中有这个引用时,就说明该Activity已经被回收了,就从retainedKeys队列移除这个key。所以,当一个Activity被destroy之后,就先把它对应的key添加到retainedKeys队列中,等到gc之后,再检测retainedKeys这个队列,如果对应的key还在,就说明发生了内存泄漏。
ActivityRefWatcher如何监视Activity
总结checkForLeak
SnapShot对象包含了所有对象引用的所有路径,就可以查找到所需要的内存泄漏路径
找出泄漏的对象/找出泄漏对象的最短路径
findLeakingReference():找出泄漏的对象
总结findLeakingReference
(1)在snapshot快照中找到第一个弱引用
(2)遍历这个对象的所有实例
(3)如果key值和最开始定义封装的key值相同,那么返回这个泄漏对象
findLeakTrace(): 找出泄漏对象的最短路径
LeakCanary里面的GC Roots只关注两种类型:
① 静态的
② 这个对象被其他线程使用,并且其他线程正在运行没有结束
findLeakingReference()和findLeakTrace()这两个方法的主要功能总结:
(1)解析hprof文件,把这个文件封装成snapshot
(2)根据弱引用和前面定义的key值,确定泄漏的对象
(3)找到最短泄漏路径,作为结果反馈出来
当一个Activity被销毁之后,Leakcanary会在onDestory
方法中进行2次GC
(为啥要多次GC,其实是因为一次GC并不能保证对象被回收),如果熟悉JVM的伙伴应该知道,只要涉及到GC,极大的概率会触发STW
,那么这个时候就会卡顿。
LeakCanary监控泄漏利用了弱引用的特性
,为Activity创建弱引用,当Activity对象变成弱可达时(没有强引用),弱引用会被加入到引用队列
中,通过在Activity.OnDestroy()后连续触发两次GC
,并检查引用队列,可以判定Activity是否发生了泄漏。但频繁的GC会造成用户可感知的卡顿,所以Koom采用了无性能损耗的内存阈值监控来触发镜像采集。
Koom:
添加LeakCanary依赖包
https://github.com/square/leakcanary
在主模块app下的build.gradle下添加如下依赖:
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
LeakCanary.install(this);
2. 在配置文件中注册ExampleApplication
在AndroidManifest.xml中的application标签中添加如下信息:
android:name=".ExampleApplication"
这个时候安装应用到手机,会自动安装一个Leaks应用,如下图:
制造一个内存泄漏的点
建立一个ActivityManager类,单例模式,里面有一个数组用来保存Activity:
package com.example.android.sunshine.app;
import android.app.Activity;
import android.util.SparseArray;
import android.view.animation.AccelerateInterpolator;
import java.util.List;
public class ActivityManager {
private SparseArray container = new SparseArray();
private int key = 0;
private static ActivityManager mInstance;
private ActivityManager(){}
public static ActivityManager getInstance(){
if(mInstance == null){
mInstance = new ActivityManager();
}
return mInstance;
}
public void addActivity(Activity activity){
container.put(key++,activity);
}
}
然后在DetailActivity中的onCreate方法中将当前activity添加到ActivityManager的数组中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
ActivityManager.getInstance().addActivity(this);
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData());
DetailFragment fragment = new DetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.weather_detail_container, fragment)
.commit();
}
}
我们从首页跳转到详情页的时候会进入DetailActivity的onCreate的方法,然后就将当前activity添加到了数组中,当返回时,我们没把他从数组中删除。再次进入的时候,会创建新的activity 并添加到数组中,但是之前的activity仍然被引用,无法释放,但是这个activity不会再被使用,这个时候就造成了内存泄漏。我们来看看LeakCanary是如何报出这个问题的。