内存泄漏,是指一些对象已经不再需要,但是无法成功被gc回收,导致这部分内存无法释放,造成资源的浪费。当大量的内存泄漏堆积时,严重时还容易间接引发OOM。
例如:当Activity被销毁后,理论上activity已经不再需要,内存空间理应被释放,但是如果有个静态变量持有了这个activity的引用,就会导致gc无法回收activity,造成内存泄漏。
以下代码模拟了上述场景:MainActivity启动了TestActivity,然后再从TestActivity后退到MainActivity,理论上从TestActivity退出后执行了onDestroy(),就不再需要TestActivity,但是因为Utils的静态对象持有了TestActivity,导致TestActivity无法被gc回收,造成内存泄漏。实际开发中应该避免这种写法。
package com.bc.example;
public class Utils {
public static Context cacheContext;
}
pulic class MainActivity extends Activity {
public void onButtonClick() {
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
}
}
pulic class SecondActivity extends Activity {
@Override
public void onCreate() {
Utils.cacheContext = this;
super.onCreate();
}
}
内存泄漏发生的原因:长生命周期对象持有短生命周期对象,导致短生命周期对象在不再需要时无法被gc回收。一般是由于代码bug引起,常见的内存泄漏原因有:
例子如1.1中所示。
解决办法:静态变量或单例不要持有activity或view等对象的引用,如果必须持有引用可以改为WeakReference。
内部类会持有外部类对象的引用,例如:
public class MainActivity extends Activity {
/**
* 内部类
*/
public class InnerClass {
}
}
上述代码在编译后,生成的内部类MainActivity$InnerClass.class文件如下:
public class MainActivity$InnerClass {
public MainActivity$InnerClass(MainActivity this$0) {
this.this$0 = this$0;
}
}
可见,内部类持有外部类的引用,所以对于内部类的使用需要注意内部类的对象是否被持久引用,导致外部类无法被释放。
解决办法:考虑是否可以使用静态内部类实现,静态内部类相当于普通类,不会持有外部类的引用。
匿名内部类同样会持有外部类对象的引用,以下面匿名内部Handler类为例:
public class MainActivity extends Activity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("TAG", msg.toString());
super.handleMessage(msg);
}
};
}
上述代码在编译后,会生成匿名内部类MainActivity$1.class文件如下:
class MainActivity$1 extends Handler {
MainActivity$1(MainActivity this$0) {
this.this$0 = this$0;
}
public void handleMessage(Message msg) {
Log.d("TAG", msg.toString());
super.handleMessage(msg);
}
}
可见,匿名内部handler类持有了外部Activity的引用,这样,当Activity销毁后,如果该Handler仍有message在队列中,因为message.target指向该handler,而handler又持有了activity,导致activity无法被gc回收,发生内存泄漏。
解决办法:在销毁activity时调用handler.removeCallbacksAndMessages();考虑是否可以使用静态内部类实现,静态内部类相当于普通类,不会持有外部类的引用;当业务确实需要调用activity内的方法时,以WeakReference的方式进行调用。
首先,分析Animator动画启动的源码如下:
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
private void start(boolean playBackwards) {
// ...省略部分代码
// 重点代码
addAnimationCallback(0);
}
private void addAnimationCallback(long delay) {
// 将自己添加到单例AnimationHandler的FrameCallback中
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
@Override
public void end() {
// ...省略
endAnimation();
}
@Override
public void cancel() {
// ...省略
endAnimation();
}
private void endAnimation() {
// ...省略
removeAnimationCallback();
}
private void removeAnimationCallback() {
// 将自己从AnimationHandler的FrameCallback中移除
getAnimationHandler().removeCallback(this);
}
}
/**
* AnimationHandler是单例实现,用来接收Choreographer.FrameCallback回调完成动画
*/
public class AnimationHandler {
public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
new ArrayList<>();
public static AnimationHandler getInstance() {
if (sAnimatorHandler.get() == null) {
sAnimatorHandler.set(new AnimationHandler());
}
return sAnimatorHandler.get();
}
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
}
由以上代码分析可知,Animator在start()后会被一个单例类持有,在动画end或cancel后从callback列表中移除。
在开发过程中,如果Animator定义的匿名内部类listener又持有了activity,而动画是无限循行ValueAnimator.INFINITE,就导致动画start后如果不cancel,会发生内存泄漏。
public class MainActivity extends Activity {
private TextView textView;
private ValueAnimator animator = ValueAnimator.ofFloat(0,1);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView)findViewById(R.id.text_view);
animator.setDuration(1000);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(ValueAnimator.INFINITE);
// 匿名内部类listener会持有外部类的引用
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
Float alpha = (Float) animator.getAnimatedValue();
textView.setAlpha(alpha);
}
});
}
@Override
protected void onDestroy() {
animator.cancel();
}
}
解决方案:在activity销毁时,调用动画的cancel方法。
资源性对象(例如InputStream/OutputStream,Cursor,File文件等)未及时close,也会导致gc无法回收这部分内存。
Eventbus的实现原理是一个单例中的map持有已注册的对象(详见:Eventbus原理分析),所以如果不取消注册(EventBus.getDefault().unregister(this))就会造成内存泄漏。
此外,还有注册了BroadcastReceiver、listener、RxJava subscription但是未取消注册等,也会造成内存泄漏。在开发中可能造成内存泄漏的bug很多,对于listener、receiver、observer、callback等对象,开发者应该仔细检查引用关系是否必要或合理,是否可能造成内存泄漏。
(1)强引用(StrongReference):在GC时,不会被回收;
(2)软引用(SoftReference):内存不足时,如果一个对象只有软引用,才会被回收;
(3)弱引用(WeakReference):在GC时,如果一个对象只有弱引用,则会被回收;
(4)虚引用(PhantomReference):任何时候都有可能被回收;
hprof即heap profile文件,表示当前堆的内存快照。通过分析hprof文件,可以当前哪些对象存在泄漏。
获取hprof文件的方式有三种:
(1)AS的Profiler工具
Android studio的Profiler工具,在memory工具栏点击Dump Java heap即可捕捉当前堆内存快照。也可以导入hprof文件展示。
(2)adb命令行获取
adb shell am dumpheap <processname> <filename>
(3)代码获取
// 下面代码会暂时挂起所有线程,并将当前堆快照保存到指定的fileName文件
Debug.dumpHprofData(fileName)
以1.1中的代码为例,先启动SecondActivity,然后再返回到MainActivity;先点击profiler中的强制垃圾回收,再点击dump后,profiler工具会列出当前堆快照,并列出存在内存泄漏对象,如下所示::
在点击leak后,可看到泄漏的SecondActivity对象的引用链如下:
可根据GC root引用链修复内存泄漏。
Shark是leakcanary2中用到的堆内存分析工具。运行速度快,占用内存少,适用于在客户端上分析hprof。
如果项目中需要在app运行过程中分析hprof,可考虑使用,暂不深入研究。leakcanary1.x的Haha使用已经较少使用,不再介绍。
eclipse的MAT工具的使用方式与AS的Profiler工具的使用方式类似,也可以强制垃圾回收,导出当前堆快照hprof文件,分析hprof后展示引用链。暂不详细介绍。
综上所述,比较原始的修复内存泄漏的办法是:在Activity退出后,导出当前堆快照hprof,然后使用分析工具打开hprof文件,分析是否存在内存泄漏,并切断引用链来修复内存泄漏。
在实际开发中,这种做法很繁琐,需要每次Activity销毁后由开发者导出hprof并分析,我们希望有一个工具可以在发生内存泄漏的时候自动导出hprof文件并分析,将内存泄漏的引用链展示出来,leakcanary就提供了这样的功能。
leakcanary是一个安卓内存泄漏检测库,用来帮助开发者检测Activity、FragmentAndViewModel、RootView、Service四种对象是否存在内存泄漏。
leakcanary用法很简单,只需要在app的build.gradle中加入如下依赖
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
// 可以看出,只有在编译debug包时,才会将leakcanary的代码打入包内
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
至此,leakcanary就可以正常工作了。在app启动后,可以看到leakcanary输出的log表示leakcanary正在运行:
D LeakCanary: LeakCanary is running and ready to detect leaks
在安装app后,桌面除了开发者的app图标外,还会多出一个leak的图标可以打开leakcanary的activity。在发生内存泄漏时,leakcanary会在合适的时机(泄漏的对象数量达到默认的阈值5或Application不可见,详见第四小节)自动dump当前堆快照hprof,然后分析是否有内存泄漏,并在leakcanary的activity中展示出来。开发者也可以自己点击’Dump Heap Now’来主动触发内存泄漏检测。
仍然以1.1中例子为例,在从SecondActivity返回后,点击’Dump Heap Now’可看到leakcanary给出了内存泄漏的引用链,如下:
可见,是因为Utils引用了SecondActivity对象,导致SecondActivity无法被回收,可考虑删掉该引用,或者改用WeakReference来修复内存泄漏。
在build.gradle中依赖debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
后,可以看到引入了以下aar:
leakcanary自动检测内存泄漏,主要分为四步:
(1)自动监测Activity、FragmentAndViewModel、RootView、Service的销毁,判断是否存在内存泄漏;
(2)在发生内存泄漏时,捕捉内存快照hprof文件;
(3)在HeapAnalyzerService使用Shark分析hprof文件;
(4)将分析出的内存泄漏结果进行归类并展示。
在leakcanary1.x中,需要开发者在代码中调用leakcanary的初始化方法才能实现自动监测:
pulib class MyApplication extends Application {
@Override
public void onCreate() {
// leakcanary1.x需要开发者调用后才能实现自动监测
LeakCanary.install(this);
super.onCreate();
}
}
而在leakcanary2.x中,只需要在build.gradle中添加依赖即可,原因是因为leakcanary2.x中利用了ContentProverder在Application创建后就开始创建的原理(源码分析见第五节),在自定义的ContentProverder调用类似的manualInstall方法实现app启动后自动初始化并监测object。
leakcanary2.x中实现自动监测的ContentProverder是AppWatcherInstaller,声明见leakcanary-object-watcher-android-2.7.aar的mainfest中:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher" >
<uses-sdk android:minSdkVersion="14" />
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />
application>
manifest>
leakcanary2.x的AppWatcherInstaller源码如下:
internal sealed class AppWatcherInstaller : ContentProvider() {
internal class MainProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
// ContentProvider会在Application创建后就执行创建并执行onCreate()
// 在onCreate()里调用单例AppWatcher的manualInstall方法
AppWatcher.manualInstall(application)
return true
}
}
/**
* 单例AppWatcher
*/
object AppWatcher {
/**
* @param watchersToInstall 默认值为appDefaultWatchers(application)
*/
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
watchersToInstall.forEach {
it.install()
}
}
/**
* 默认的Watcher,有四类:ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher
*/
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
}
根据上面源码分析可知,leakcanary2.x是在application创建后就调用AppWatcher的manualInstall方法,方法里默认实现了四种watcher(ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher)来分别监测Activity、FragmentAndViewModel、RootView、Service的引用,来监测这四种对象是否存在内存泄漏。
此外,PlumberInstaller也是通过ContentProvider的方式在Application启动后就初始化,来增加对一些比较少见的对象的内存泄漏检测,这里不深入分析。
接下来,主要分析AppWatcher默认提供的四种watcher的实现方式,实际上四种Watcher主要负责在Activity、FragmentAndViewModel、RootView、Service销毁时传递引用,objectWatcher以弱引用的形式间隔一段时间分析当前对象是否被回收,如果已经被回收则不存在内存泄漏,否则说明可能存在内存泄漏。以下是源码分析:
object AppWatcher {
/**
* objectWatcher用来检测当前正在监测的对象是否可能存在内存泄漏
*/
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
// retainedDelayMillis默认为5s
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
// 重点,四种Watcher都将objectWatcher作为参数传递
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
}
以ActivityWatcher为例,初始化时时通过注册ActivityLifecycleCallback实现Activity销毁时收到回调,收到回调后将引用传递给objectWatcher,objectWatcher持有该Activity的弱引用,并在一定时间后(默认5s)分析是否被回收。
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// 由ObjectWatcher分析expectWeaklyReachable,判断Activity在destroy后是否可能存在内存泄漏
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
类似的,FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher主要负责在对象销毁时将引用传递给objectWatcher,由objectWatcher判断是否被回收,主要区别是判断销毁的时机有所区别,Activty、Fragment、RootView通过注册系统回调,Service通过Proxy.newProxyInstance()代理方式实现。
接下来,主要分析ObjectWatcher收到expectWeaklyReachable()回调时如何判断对象是否被回收。
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
/**
* 以弱引用形式保存当前正在观察的对象
*/
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
/**
* 所有弱引用对象注册到的ReferenceQueue,用来判断弱引用所指向的对象是否被回收
*/
private val queue = ReferenceQueue<Any>()
/**
* 当检测到对象可能发生内存泄漏时的listener,4.2小节再做分析
*/
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
/**
* 在Activity销毁后,以弱引用方式持有其引用,用于后续判断是否被回收
*/
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 弱引用持有Activity
val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
// 将Activity的弱引用添加到观察列表里
watchedObjects[key] = reference
// 由checkRetainedExecutor间隔5s在主线程判断对象是否被回收,checkRetainedExecutor的创建方法在AppWatcher里,不再分析
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
/**
* 在当前观察的对象列表中,将已经被回收的对象移除,剩余的其他对象就是可能发生泄漏的对象
*/
@Synchronized private fun moveToRetained(key: String) {
// 移除掉已经被回收的对象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 剩余可能发生泄漏的对象,则会调Listener的方法做进一步分析(4.2小节分析源码)
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
// ReferenceQueue中有该WeakReference,则说明该WeakReference指向的对象已经被回收
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
}
上面提到,在Activity执行onDestroy()时,Activity对象以KeyedWeakReference弱引用的形式被ObjectWatcher持有,KeyedWeakReference本质上是WeakReference:
class KeyedWeakReference(referent: Any, val key: String, val description: String,
val watchUptimeMillis: Long,referenceQueue: ReferenceQueue<Any>)
: WeakReference<Any>(referent, referenceQueue) {
}
如何判断一个对象是否被回收,是通过WeakReference与ReferenceQueue的关系实现的:在创建WeakReference对象时可以指定一个ReferenceQueue对象,当该WeakReference指向的对象被GC标记可以回收后,该WeakReference会被加入到ReferenceQueue的末尾。WeakReference有两个构造函数,源码如下:
public class WeakReference<T> extends Reference<T> {
/**
* 创建一个指向传入对象的弱引用
*/
public WeakReference(T referent) {
super(referent);
}
/**
* 创建一个指向传入对象的弱引用,并且将该弱引用注册到指定ReferenceQueue
* 当弱引用所指向的对象被GC标记可以回收后,该弱引用会被放到ReferenceQueue的末尾
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
leakcanary2.x利用ContentProvider在Application创建后就创建的原理,在ContentProvider创建时即完成leakcanary初始化,方便开发者使用。
leakcanary通过ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher在对象销毁时将引用传递给objectWatcher,由objectWatcher判断对象是否可能存在内存泄漏。
objectWatcher在Activity、FragmentAndViewModel、RootView、Service销毁5s后,在主线程判断对象是否只被GC标记回收,如果没有则认为可能存在内存泄漏,接着触发HeapDumpTrigger(在4.2小节分析)在工作线程再次检查是否存在内存泄漏,是则导出hpfo文件并分析内存泄漏的引用链。
objectWatcher判断观察的对象是否被回收的原理是:在创建WeakReference对象时可以指定一个ReferenceQueue对象,当该WeakReference指向的对象被GC标记可以回收后,该WeakReference会被加入到ReferenceQueue的末尾。
上面讲到,objectWatcher在认为可能发生内存泄漏后会回调OnObjectRetainedListener的onObjectRetained()方法:
onObjectRetainedListeners.forEach { it.onObjectRetained() }
那么,接下来继续分析AppWatcher.objectWatcher.addOnObjectRetainedListener(this)是在什么时机添加的,添加的时机也是在AppWatcher的manualInstall方法里:
object AppWatcher {
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
// 重点分析
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
it.install()
}
}
}
internal object LeakCanaryDelegate {
val loadLeakCanary by lazy {
try {
// 实现类是InternalLeakCanary
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE").get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
}
收到可能存在内存泄漏的回调后,主要逻辑是在InternalLeakCanary中完成,源码如下:
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
private var _application: Application? = null
override fun invoke(application: Application) {
_application = application
// 这里就是objectWatcher.addOnObjectRetainedListener的时机,可见是在初始化时就添加好的
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
// 触发GC的实现
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
// 工作线程
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// heapDump的触发器,在工作线程
heapDumpTrigger = HeapDumpTrigger(application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,configProvider)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
// 添加桌面快捷方式
addDynamicShortcut(application)
// logcat相关 We post so that the log happens after Application.onCreate()
mainHandler.post {
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}
}
根据以上分析,收到可能存在内存泄漏的回调后,接下来主要逻辑是在InternalLeakCanary中完成,接着分析收到回调后做了哪些工作,源码如下:
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
// heapDumpTrigger是在上述InternalLeakCanary.invoke方法里创建的
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
}
HeapDumpTrigger会在工作线程再次校验一次是否存在内存泄漏,检验方式是触发一次gc,然后检查是否仍有对象未被GC标记回收,如果是则进行dump heap,然后调用HeapAnalyzerService对hprof文件做进一步分析:
internal class HeapDumpTrigger(
private val application: Application,
private val backgroundHandler: Handler,
private val objectWatcher: ObjectWatcher,
private val gcTrigger: GcTrigger,
private val heapDumper: HeapDumper,
private val configProvider: () -> Config
) {
/**
* 在工作线程调度再次校验是否存在内存泄漏
* 是则dump heap并做进一步分析
* */
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
// 工作线程调度
backgroundHandler.postDelayed({
checkScheduledAt = 0
// 主要方法,下面分析
checkRetainedObjects()
}, delayMillis)
}
/**
* 触发gc再次确认是否存在内存泄漏,是则执行dump heap
* */
private fun checkRetainedObjects() {
val config = configProvider()
// 1.查看当前objectWatcher持有的对象
var retainedReferenceCount = objectWatcher.retainedObjectCount
// 2.objectWatcher持有对象时,先触发一次gc
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 3.gc后,如果objectWatcher持有的引用数小于5,则return
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// 4.gc后,如果objectWatcher持有的引用数大于5,则dump heap做进一步分析
// 每次dump heap之间的间隔需要>=60s,小于60s则return,并延迟到间隔满足60s再执行
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
// 间隔小于60s,则延迟一段时间后再执行heap dump
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
// 5.执行dump heap
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
/**
* 首先Debug.dumpHprofData,然后调用HeapAnalyzerService对hprof文件做进一步分析
* */
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// 1.heapDumper.dumpHeap()里面执行了Debug.dumpHprofData(heapDumpFile.absolutePath)来dump heap
when (val heapDumpResult = heapDumper.dumpHeap()) {
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 2.调用HeapAnalyzerService对hprof文件做进一步分析
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
}
HeapDumpTrigger中触发垃圾回收是由GcTrigger实现的,源码单独分析如下:
interface GcTrigger {
fun runGc()
/**
* GcTrigger的默认实现
*/
object Default : GcTrigger {
override fun runGc() {
// 调用gc()并不保证一定会执行gc,所以这里gc后线程暂停了100ms,然后又再次触发了gc
Runtime.getRuntime().gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
ObjectWatcher只能检测到当前有对象可能存在内存泄漏,在HeapDumpTrigger会在工作线程再次校验一次是否存在内存泄漏,检验方式是触发一次gc,然后检查是否有对象未被GC标记回收,如果是则表示发生了内存泄漏,接着进行dump heap,然后调用HeapAnalyzerService对hprof文件做进一步分析。
每次HeapDumpTrigger进行dump heap有60s时间间隔限制,因为dump heap会暂时挂起当前进程中的所有java线程。
在HeapDumpTrigger把hprof文件输出后,会调用HeapAnalyzerService启动服务去分析hprof文件,得到最终的内存泄漏分析结果。源码如下:
internal class HeapAnalyzerService : ForegroundService(
HeapAnalyzerService::class.java.simpleName,
R.string.leak_canary_notification_analysing,
R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {
companion object {
fun runAnalysis(context: Context,heapDumpFile: File,heapDumpDurationMillis: heapDumpReason: String = "Unknown") {
val intent = Intent(context, HeapAnalyzerService::class.java)
// 将heapDumpFile封装到intent,传给HeapAnalyzerService分析
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
startForegroundService(context, intent)
}
}
override fun onHandleIntentInForeground(intent: Intent?) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
// 1.获取hprof文件路径
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
// 2.分析hprof文件
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
// 3.分析结果被封装成HeapAnalysisSuccess对象
is HeapAnalysisSuccess -> heapAnalysis.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
)
is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// 4.将分析结果回调给listern,更新UI将分析结果展示给开发者
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}
}
对于Shark分析hprof文件的代码暂不深入研究,下面给出了分析结果的封装类HeapAnalysisSuccess:
data class HeapAnalysisSuccess(
override val heapDumpFile: File,
override val createdAtTimeMillis: Long,
override val dumpDurationMillis: Long = DUMP_DURATION_UNKNOWN,
override val analysisDurationMillis: Long,
val metadata: Map<String, String>,
/**
* 当前APP内泄漏的对象列表,开发者主要关注
*/
val applicationLeaks: List<ApplicationLeak>,
/**
* 当前APP内第三方库内泄漏的对象列表,开发者不一定可以修复
*/
val libraryLeaks: List<LibraryLeak>,
val unreachableObjects: List<LeakTraceObject>
) : HeapAnalysis() {
/**
* 所有泄漏的对象
*/
val allLeaks: Sequence<Leak>
get() = applicationLeaks.asSequence() + libraryLeaks.asSequence()
}
在4.1中介绍过,ContentProvider是在Application创建后就创建的,且ContentProvider的onCreate时机在Application的onCreate时机之前。
下面从Android-30源码分析,在AMS启动Application后,会执行到ActivityThread.H类中,源码如下:
public final class ActivityThread extends ClientTransactionHandler {
class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
// 1.启动Application
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
// 下面分析
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
// 退出Application
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
// ...省略
}
}
}
private void handleBindApplication(AppBindData data) {
// 1.设置进程的一些信息
VMRuntime.registerSensitiveThread();
Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
Process.setArgV0(data.processName);
android.ddm.DdmHandleAppName.setAppName(data.processName,
data.appInfo.packageName,
UserHandle.myUserId());
VMRuntime.setProcessPackageName(data.appInfo.packageName);
VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);
// 2.创建AppContext和Instrumentation
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo, appContext.getClassLoader(), false, true, false);
mInstrumentation = (Instrumentation)cl.loadClass(data.instrumentationName.getClassName()).newInstance();
final ComponentName component = new ComponentName(ii.packageName, ii.name);
mInstrumentation.init(this, instrContext, appContext, component,
data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
// 3.创建Application
Application app;
app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
// 4.创建ContentProvider
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
// 下面分析
installContentProviders(app, data.providers);
}
}
// 5.执行Application的onCreate()方法
mInstrumentation.onCreate(data.instrumentationArgs);
mInstrumentation.callApplicationOnCreate(app);
}
private void installContentProviders(Context context, List<ProviderInfo> providers) {
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
// 实例化ContentProviders,下面分析
ContentProviderHolder cph = installProvider(context, null, cpi, false, true, true);
if (cph != null) {
cph.noReleaseNeeded = true;
results.add(cph);
}
}
ActivityManager.getService().publishContentProviders(
getApplicationThread(), results);
}
private ContentProviderHolder installProvider(Context context,
ContentProviderHolder holder, ProviderInfo info,
boolean noisy, boolean noReleaseNeeded, boolean stable) {
ContentProvider localProvider = null;
final java.lang.ClassLoader cl = c.getClassLoader();
lLoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
if (packageInfo == null) {
packageInfo = getSystemContext().mPackageInfo;
}
// 1.实例化ContentProvider
localProvider = packageInfo.getAppFactory().instantiateProvider(cl, info.name);
// 2.调用ContentProvider对象的attachInfo,里面会调用ContentProvider的onCreate()方法
localProvider.attachInfo(c, info);
}
}
欢迎关注我,一起解锁更多技能:BC的掘金主页~ BC的CSDN主页~
leakcanary官方文档:https://square.github.io/leakcanary/
hprof简介:http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html