鉴于笔者能力有限,如有疏漏错误之处,敬请原谅,本文只做抛砖引玉的作用
一、内存泄露介绍
内存泄露基本上都是由于不恰当的使用,当对象使用完了之后,还存在强引用,导致该释放的时候,没有释放,一直占用内存,我想是很多人会遇到的问题,一般的解决思路是生成hprof文件,再用mat等内存分析工具来查看,找到怀疑点,然后对照着源码再来验证。这里有一个很好的工具,可以在运行期间就能检测到内存泄露,并且输出最短路径,它就是检测内存泄露的神器LeakCanary。
二、leakcanary介绍
相关的使用方法可以参考:
https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/,里面有详细的介绍,使用也比较简单。
首先在自定义的Application中onCreate中调用下面即可
public void onCreate() {
super.onCreate();
enabledStrictMode();//开启严格模式
LeakCanary.install(this);
}
private void enabledStrictMode() {
if (SDK_INT >= GINGERBREAD) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
.detectAll() //
.penaltyLog() //
.penaltyDeath() //
.build());
}
上面的使用是不是非常简洁,基本不需要什么操作就可以直接使用,那么它是怎么实现的呢。
三、从源码角度分析leakcanary
我们从源码的角度来分析LeakCanary的原理,LeakCanary源码地址:
https://github.com/square/leakcanary,下面来看一下源码的目录结构
leakcanary
|------ leakcanary-analyzer 内存泄露文件分析器
|------ leakcanary-android 内存泄露平台android相关的具体实现,比如生成hprof,调用内存泄露检测,调用内存泄露分析器,以及显示最短路径
|------ leakcanary-android-no-op 从名字可以知道没有做任何操作
|------ leakcanary-sample 一个使用的sample
|------ leakcanary-watcher 整个内存泄露检测框架的抽象实现,独立于android系统
|------ 其他
根据模块来分析一下,具体如何实现的
1.内存泄露的检测
上图是leakcanary-watcher主要接口的UML类图,比较粗糙。RefWatcherBuilder.build构造其他几个类,作为RefWatcher的构造参数最终生成RefWatcher。
具体过程描述一下,当需要查看某个对象是否存在内存泄露,调用RefWatcher.watch方法,然后调用RefWatcher.ensureGoneAsync来调用WatchExecutor.execute执行异步操作,该操作描述一下,首先确认该对象是否已经被释放,如果没有,再次调用GcTrgger.runGc进行垃圾回收,然后再次确认该对象是否已经被释放,如果没有,则调用HeapDumper.dumpHeap()方法,生成heapdump文件,最后交给分析器分析。
2.内存泄露分析
leakcanary-analyzer负责这一功能,内存泄露的核心类是HeapAnalyzer,流程比较简单,但是具体实现细节相当复杂,主要用到了一个库-com.squareup.haha用于分析hprof文件的,下面看一下核心代码流程。
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
描述一下流程:
(1).首先将内存泄露文件(hprof)直接映射到内存(映射到虚拟内存),也就是下面操作
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile)
(2).解析hprof文件,生成堆快照(Snapshot),也就是下面操作,这个是根据hprof文件格式来解析的,有兴趣可以自己研究一下。
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
(3).根据快照,和gc根节点的名称,去掉重复的gc根节点,也就是下面操作
deduplicateGcRoots(snapshot);
(4).根据关键字,找到泄露引用实例
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
(5).然后找到内存泄露的最短强引用路径
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
最后将结果返回。
3.内存泄露检测、分析还有显示的整个调用过程
我们看leakcanary-android这个工程,LeakCanary作为入口,调用install方法
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
上述是一个链式调用,
refWatcher
(application)初始化AndroidRefWatcherBuilder(继承自RefWatcherBuilder
),listenerServiceClass初始化dump listener即ServiceHeapDumpListener(实现HeapDump.Listener接口,当生成dump file时,将内存泄露文件扔给分析器的接口),其中DisplayLeakService(继承自AbstractAnalysisResultService)处理分析完之后的结果的,buildAndInstall方法调用了上述RefWatcherBuilder.build方法,接着在Application中注册监听Activity生命周期的监听类。
if (refWatcher != DISABLED) {
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
LeakCanary.enableDisplayLeakActivity(context);
}
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
}
注册监听类
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
监听类
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
当Activity调用destroy之后,会调用ActivityRefWatcher.this.onActivityDestroyed方法,开始监听Activity是否有内存泄露。
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
开始了上述描述的watch流程
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
这里需要注意的是使用了KeyedWeakReference(继承自WeakReference)是一个弱引用,并且加入到了ReferenceQueue中,遍历的这个队列可以知道哪些对象是否被回收,代码中用了一个Set来管理队列的对象,当对象不存在,从set中去掉。
检测过程在上面已经简单描述过,我们看下代码
(1)初始化RefWatcher
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
ExcludedRefs excludedRefs = this.excludedRefs;
if (excludedRefs == null) {
excludedRefs = defaultExcludedRefs();
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
excludedRefs);
}
(2)调用watch方法,上面有描述
(3)再次调用gc,生成dump file
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
(4)扔给分析器分析
ServiceHeapDumpListener的analyze方法
public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
HeapAnalyzerService的runAnalysis和onHandleIntent,最后扔给了HeapAnalyzer来分析处理
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
(5)将最终的结果DisplayLeakService生成notification
AbstractAnalysisResultService调用sendResultToListener将结果扔到onHeapAnalyzed处理,onHeapAnalyzed在DisplayLeakService中实现的。
用户点击通知,可以启动DisplayLeakActivity显示内容。
鉴于笔者能力有限,分析过于粗糙,无法将精华部分呈现给读者,读者可以自行去查看源码流程和查找相关文档。源码地址:
https://github.com/square/leakcanary