内存泄漏(Memory Leak)
程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
内存溢出:
应用系统中存在无法回收或使用的内存过多,最终
使得程序运行要用到的内存大于能提供的最大内存。
- StackOverflow:因为递归太深发生堆栈溢出
- OutOfMemory:内存占有量超过了虚拟机分配的最大值
OOM出现情况:
- 加载图片过多过大
- 分配特大数组
- 内存资源过多来不及释放等
Android常见内存泄露
1、静态成员变量持有外部(短周期临时)对象引用。 如单例类(类内部静态属性)持有一个activity(或其他短周期对象)引用时,导致被持有的对象内存无法释放。
2、内部类。当内部类与外部类生命周期不一致时,就会造成内存泄漏。如非静态内部类创建静态实例、Activity中的Handler或Thread等。
3、资源没有及时关闭。如数据库、IO流、Bitmap、注册的相关服务、webview、动画等。
4、集合内部Item没有置空。
5、方法块内不使用的对象,没有及时置空。
LeakCanary
LeakCanary提供了一种很方便的方式,让我们在开发阶段测试内存泄露,我们不需要自己根据内存块来分析内存泄露的原因,我们只需要在项目中集成他,然后他就会帮我们检测内存泄露,并给出内存泄露的引用链
集成&基本使用
- 在gradle中添加依赖,这种不区分debug和release
implementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'
- Application 的onCreate方法install,生成mRefWatcher
public class App extends Application {
private RefWatcher mRefWatcher;
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
mRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
App application = (App) context.getApplicationContext();
return application.mRefWatcher;
}
}
- Fragment监听(refWatcher.watch(this);)
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = App.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
基本原理
- 主要在Activity的onDestroy方法中,手动调用 GC
- 利用ReferenceQueue+WeakReference,来判断是否有释放不掉的引用
- 结合dump memory的hprof(堆存储文件)文件, 用HaHa分析出泄漏位置
源码分析
LeakCanary会单独开一进程,用来执行分析任务,和监听任务分开处理。
-
方法install中主要是构造来一个RefWatcher,用于监控、追踪应用中的对象引用。
监听原理在于 Application 的
registerActivityLifecycleCallbacks方法,可以对应用内所有 Activity 的生命周期做监听, LeakCanary只监听了
Destroy方法。
这里分析对象是否内存泄露的是RefWatcher类,下面简单介绍一下这个类的成员变量
- WatchExecutor watchExecutor:确保任务在主线程进行,同时默认延迟5s执行任务,留时间给系统GC
-DebuggerControl debuggerControl:控制中心
-GcTrigger gcTrigger:内部调用Runtime.getRuntime().gc(),手动触发GC
-HeapDumper heapDumper:用于创建.hprof文件,用于储存head堆快照,可以知道哪些程序在大部分使用内存
-HeapDump.Listener heapdumpListener:分析结果完成后会告诉这个监听器
-ExcludedRefs excludedRefs:分析内存泄露的白名单
- Activity的OnDestroy() 中回调refWatcher.watch()
- KeyedWeakReference是WeakReference类的子类,用了 KeyedWeakReference(referent, key, name, ReferenceQueue )的构造方法,将监听的对象(activity)引用传递进来,并且New出一个ReferenceQueue来监听GC后 的回收情况。
- 方法ensureGone中通过检测referenceQueue队列中的引用情况,来判断回收情况,通过手动GC来进一步确认回收情况。整个过程肯定是个耗时卡UI的,整个过程会在WatchExecutor中执行
用watchExecutor去调度分析任务,这个主要是保证,在主线程进行,延迟5s,让系统有时间GC
- LeakCanary已经利用Looper机制做了一定优化,利用主线程空闲的时候执行检测任务,WatchExecutor中Looper中的MessageQueue有个mIdleHandlers队列,在获取下个要执行的Message时,如果没有发现可执行的下个Msg,就会回调queueIdle()方法。
首先通过
removeWeaklyReachableReferences()
方法,尝试从弱引用队列获取待分析对象,如果不为空说明被系统回收了,就把retainedKeys中的key值移除,如果被系统回收直接返回DONE,如果没有被系统回收,就手动调用 gcTrigger.runGc();手动触发系统gc,然后再次调用removeWeaklyReachableReferences()方法,如过还是为空,则判断为内存泄露手动触发GC后,调用enqueueReferences方法沉睡100ms,给系统GC时间, System.runFinalization();,这个是强制调用已失去引用对象的finalize方法
确定有内存泄漏后,调用heapDumper.dumpHeap();生成.hprof文件,然后回调到heapdumpListener监听,这个监听实现是ServiceHeapDumpListener类,会调analyze()方法
HeapDump是一个modle类,里面用于储存一些分析类强引用的需要信息 HeapAnalyzerService.runAnalysis方法是发送了一个intent,启动了HeapAnalyzerService服务,这是一个intentService
启动服务后,会在onHandleIntent方法启动分析,找到内存泄露的引用关系
找到引用关系
- 启动分析内存泄露的服务后,会实例化一个
HeapAnalyzer
对象,然后调用checkForLeak
方法来分析最终得到的结果, -
checkForLeak
这里用到了Square的另一个库haha,哈哈哈哈哈,名字真的就是叫这个,开源地址:github.com/square/haha… - 得到结果后调用
AbstractAnalysisResultService.sendResultToListener()
方法,这个方法启动了另一个服务
public static void sendResultToListener(Context context, String listenerServiceClassName,
HeapDump heapDump, AnalysisResult result) {
Class<?> listenerServiceClass;
try {
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
intent.putExtra(RESULT_EXTRA, result);
context.startService(intent);
}
listenerServiceClassName就是开始LeakCanary.install方法传入的DisplayLeakService,它本身也是一个intentService
@Override
protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
然后调用自身的onHeapAnalyzed方法
protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", new Object[]{leakInfo});
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if(shouldSaveResult) {
heapDump = this.renameHeapdump(heapDump);
resultSaved = this.saveResult(heapDump, result);
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
// 设置消息通知的 pendingIntent/contentTitle/contentText
...
int notificationId1 = (int)(SystemClock.uptimeMillis() / 1000L);
LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId1);
this.afterDefaultHandling(heapDump, result, leakInfo);
}
这个方法首先判断是否需要把信息存到本地,如果需要就存到本地,然后设置消息通知的基本信息,最后通过LeakCanaryInternals.showNotification方法调用系统的系统通知栏,告诉用户有内存泄露
总结
1,用ActivityLifecycleCallbacks接口来检测Activity生命周期
2,WeakReference + ReferenceQueue来监听对象回收情况
3,Apolication中可通过processName判断是否是任务执行进程
4,MessageQueue中加入一个IdleHandler来得到主线程空闲回调
- LeakCanary2.0解析 (不用HAHA,改用shark分析hprof)