文章目錄
1 Java和C/C++語言的內存泄漏
在C/C++語言開發過程中,比如C語言 malloc
分配內存 free
釋放內存,C++是 new Object
分配內存 delete object
釋放內存,對象的內存分配回收都需要程序員下意識的去維護,否則很容易出現內存泄漏。
但在Java中卻沒有這種情況,我們使用Java開發很多時候只需要 new Object
創建對象,使用對象後的無效內存都讓GC回收了。但也存在GC無法回收的情況導致JVM內存泄漏:長週期的對象引用一直持有着短週期的對象引用,GC以爲短週期對象引用還被使用,GC沒有回收,所以導致了內存泄漏。
那麼,Java是怎麼判斷對象的引用還被使用?Java垃圾回收機制是怎樣的?
2 垃圾回收機制
2.1 引用計數法
引用計數法
簡單理解就是記錄一個對象被引用的次數,一個引用被使用引用計數器就+1,反之就-1,當引用次數爲0就說明是一個垃圾對象可以被回收了。
引用計數法實現非常簡單,但也有存在的問題:循環引用,即對象a和對象b各自持有雙方的引用,導致GC無法回收,也就導致內存泄漏。
2.2 可達性分析法
可達性分析法
根據是否被GC Root引用確認是否是垃圾對象要被GC回收。
可以作爲GC Root的對象有:
-
在線程棧中的局部變量(即正在被調用的方法裏面的參數和局部變量)
-
存活的線程對象
-
JNI的引用
-
Class對象(在Android中Class被加載後是不會被卸載的)
-
引用類型的靜態變量
3 LeakCanary 1.6.x(1.6.3)
3.1 LeakCanary.install()
3.1.1 LeakCanary.install()源碼分析
LeakCanary
是內存泄漏檢測工具,在使用上一句代碼即可(使用LeakCanary版本爲1.6.3):
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// LeakCanary是在另外一個進程中啓動
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
}
}
通過一句代碼 LeakCanary.install(this)
就可以實現監聽內存泄漏。具體看下原理:
LeakCanary.java
public final class LeanCanary {
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludeRefs(AndroidExcludedRefs.createAppDefaults().build()
.buildAndInstall();
}
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
}
AndroidRefWatcherBuilder.java
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return headDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
}
上面的代碼有幾點需要說明:
-
DisplayService
是發生內存泄漏時的通知服務 -
excludedRefs()
是排除Android源碼出現的內存泄漏問題
最主要的是 AndroidRefWatcherBuilder.buildAndInstall()
:
AndroidRefWatcherBuilder.java
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
private boolean watchActivities = true;
private boolean watchFragments = true;
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
// 根據app包名生成LeakCanary關聯應用
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
// 監聽Activity
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 監聽Fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installRefWatcher = refWatcher;
return refWatcher;
}
}
LeakCanaryInternals.java
public final class LeakCanaryInternals {
public static void setEnabledAsync(Context context, final Class<?> componentClass, final boolean enabled) {
final Context appContext = context.getApplicationContext();
// 開啓子線程處理
AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnnable() {
@Override public void run() {
setEnabledBlocking(appContext, componentClass, enabled);
}
});
}
public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
}
ActivityRefWatcher.java
public final class ActivityRefWatcher {
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// 最終是通過在Application註冊監聽每個Activity的生命週期,然後轉發給RefWatcher
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
}
}
FragmentRefWatcher.java
public interface FragmentRefWatcher {
final class Helper {
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor<?> constructor = fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
FragmentRefWatcher supportFragmentRefWatcher =
(FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
}
if (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatcher);
// 和Activity一樣也是監聽Fragment生命週期轉發給RefWatcher
Application application = (Application) context.getApplication();
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
}
}
}
AndroidOFragmentRefWatcher.java
class AndroidOFragmentRefWatcher implements FragmentRefWatcher {
private final RefWatcher refWatcher;
AndroidOFragmentRefWatcher(RefWatcher refWatcher) {
this.refWatcher = refWatcher;
}
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override publci void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
}
@Override public void watchFragments(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
上面的處理可以用兩個步驟說明:
-
根據app的包名生成
LeakCanary
的關聯應用 -
通過
Application.registerActivityLifecycleCallbacks()
監聽Activity以及FragmentManager.registerFragmentLifecycleCallbacks()
監聽Fragment的生命週期並轉發給對應RefWatcher
處理
這一步生命週期監聽處理簡單理解就是:
final RefWatcher refWatcher = xxxx;
// 監聽Activity生命週期
application.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityDestroyed(Activity activity) {
watcher.watch(activity);
}
});
// 監聽Fragment生命週期
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
FragmentManager fm = activity.getFragmentManager();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
fm.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
View view = f.getView();
if (view != null) {
watcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
watcher.watch(f);
}
}, true);
}
}
});
3.1.2 LeakCanary.install()小結
總結一下我們調用 LeakCanary.install()
的處理:
-
創建
AndroidRefWatcherBuilder
構建LeakCanary
所需參數,如提供DisplayLeakService
內存泄漏通知服務,排除Android系統源碼出現的內存泄漏 -
創建
RefWatcher
,關聯app包名生成LeakCanary
應用,通過registerXxxLifecycleCallbacks()
監聽生命週期轉發給RefWatcher
處理
3.2 RefWatcher.watch()
3.2.1 RefWatcher.watch()源碼分析
RefWatcher.java
public final class RefWatcher {
public void watch(Object watchedReference) {
watch(watchReference, "");
}
public void watch(Object watchReference, String referenceName) {
if (this == DISABLED) {
return;
}
final long watchStartNanoTime = System.nanoTime();
// 爲監聽引用watchReference(Activity或Fragment)生成唯一ID
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
// watchReference就是監聽的引用對象
final KeyedWeakReference reference =
new KeyedWeakReference(watchReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
}
上面的代碼有一個對象需要注意:KeyedWeakReference
。
LeakCanary
是通過 KeyedWeakReference
來確認 watchReference(Activity or Fragment)
是否已經被回收的。我們可以通過弱引用的 ReferenceQueue
確認隊列中是否有數據,如果有就說明 watchReference
被GC回收了。具體可以看我寫的一篇文章:Java的四種引用類型
還有 watchExecutor
又是什麼?這需要回到 RefWatcher
被構建時的代碼:
RefWatcherBuilder.java
public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {
public final RefWatcher build() {
...
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
// AndroidWatchExecutor
watchExecutor = defaultWatchExecutor();
}
...
return new RefWatcher(watchExecutor, ...);
}
}
AndroidRefWatcherBuilder.java
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
AndroidWatchExecutor.java
public final class AndroidWatchExecutor implements WatchExecutor {
private final Handler mainHandler;
private final hnadler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
@Override public void execute(@NonNull Retryable retryable) {
// 第一次進入肯定是主線程,因爲RefWatcher是在主線程創建
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread
// 保證在主線程處於空閒狀態時處理
// 因爲只有主線程生命週期都走完後纔有可能正常垃圾回收
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false; // 返回false表示只會執行一次,否則會一直執行
}
});
}
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelay(new Runnable() {
@Override public void run() {
// 在子線程執行Retryable
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
watchExecutor
是 AndroidWatchExecutor
,在 RefWatcher
創建的時候會在子線程執行 Retryable.run()
。
接下來繼續看 Retryable.run()
往後的操作:
public final class RefWatcher {
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 將GC掉的對象從內存泄漏的懷疑名單中移除
removeWeaklyReachableReference();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 如果名單沒有內存泄漏的引用對象
// 說明在某次GC已經回收對象,沒有內存泄漏,不需要處理
if (gone(reference)) {
return DONE;
}
// 執行一次GC
gcTrigger.runGc();
// 再檢查引用對象是否被回收
removeWeaklyReachableReferences();
// 引用對象沒有被回收還在懷疑名單,說明已經內存泄漏,dump
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMills(startDumpHeap - gcStartNanoTime);
// 創建dump文件,創建通知提示dump
File heapDumpFile = heapDumper.dumpHeap();
// dump文件創建失敗,重試
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMills(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referencekey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 分析dump文件
heapdumpListener.analyze(heapDump);
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReference() {
// WeakReferences are enqueued as soon as the object to which they point to be becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
}
ensureGone()
主要做的事情:將GC掉的對象從內存泄漏的懷疑名單中移除,如果沒有會執行GC後再檢查是否從懷疑名單中移除,如果沒有說明內存泄漏,創建dump文件,分析dump文件。
3.2.2 RefWatcher.watch()小結
總結一下 RefWatcher.watch()
的處理:
-
爲每一個監聽引用對象(Activity或Fragment)提供唯一ID,在主線程空閒走完生命週期時通過
Retryable.run()
檢測內存泄漏 -
Retryable.run()
會調用ensureGone()
檢查,如果引用對象仍沒有被GC回收仍在ReferenceQueue
隊列說明內存泄漏,創建dump文件
3.3 HeapDumper.dumpHeap()
通過 HeapDumper.dumpHeap()
dump文件,那它是怎麼dump文件的?繼續分析代碼:
AndroidHeapDumper.java
public final class AndroidHeapDumper implements HeapDumper {
@Override @Nullable
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
// 創建通知提示dump文件,分析進度會在啓動前臺分析服務時更新
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
Toast toast = waitingForToast.get();
try {
// 最終使用的是Android提供的工具dump數據到hprof文件
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
notificationManager.cancel(notificationId);
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
}
DefaultLeakDirectoryProvider.java
public final class DefaultLeakDirectoryProvider implements LeakDirectoryProvider {
// 最多7個dump文件
private static final int DEFAULT_MAX_STORED_HEAP_DUMPS = 7;
private static final String HPROF_SUFFIX = ".hprof";
private static final String PENDING_HEAPDUMP_SUFFIX = "_pending" + HPROF_SUFFIX;
private final int maxStoredHeapDumps;
public DefaultLeakDirectoryProvider(@NonNull Context context) {
this(context, DEFAULT_MAX_STORED_HEAP_DUMPS);
}
public DefaultLeakDirectoryProvider(@NonNoll Context context, int maxStoredHeapDumps) {
if (maxStoredHeapDumps < 1) {
throw new IllegalArgumentException("maxStoredHeapDumps must be at leasts 1");
}
this.context = context.getApplicationContext();
this.maxStoredHeapDumps = maxStoredHeapDumps;
}
@Override public @Nullable File newHeapDumpFile() {
// 從外部存儲查找對應的後綴dump文件
List<File> pendingHeapDumps = listFiles(new FilenameFilter() {
@Override public boolean accept(File dir, String filename) {
return filename.endsWith(PENDING_HEAPDUMP_SUFFIX);
}
});
...
// 查找到hprof後綴的dump文件,如果dump文件多於7個,刪除修改時間較前的hprof文件
cleanupOldHeapDumps();
// 檢查外部存儲權限,如果沒有存儲權限LeakCanary無法dump文件
File storageDirectory = externalStorageDirectory();
if (!directoryWritableAfterMkdirs(storageDirectory)) {
if (!hasStoragePermission()) {
CanaryLog.d("WRITE_EXTERNAL_STORAGE permission not granted");
requestWritePermissionNotification();
} else {
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)) {
CanaryLog.d("External storage not mounted. state: %s", state);
} else {
CanaryLog.d("Could not create heap dump directory in external storage: [%s]", storageDirectory.getAbsolutePath());
}
}
}
...
return new File(storageDirectory, UUID.randomUUID().toString() + PENDING_HEAPDUMP_SUFFIX);
}
}
排除權限和dump文件檢查外,最主要的是一句代碼:Debug.dumpHprofData()
,最終是由Android的工具幫助生成hprof文件。
3.4 HeapDumpListener.analyze()
ServiceHeapDumpListener.java
public final class ServiceHeapDumpListener implements HeapDump.Listener {
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
HeapAnalyzerService.java
// ForegroundService extends IntentService
public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabledBlocking(context, HeapAnalyzerService.class, true);
setEnabledBlocking(context, listenerServiceClass, true);
// 啓動一個前臺服務分析,跨進程通信Intent提供分析所需參數
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
ContextCompat.startForegroundService(context, intent);
}
// LeakCanary是在另一個進程的,所以這裏的分析也是在另一個進程
@Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses);
// 開始分析
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 分析結果回調
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
}
HeapAnalyzer.java
public final class HeapAnalyzer {
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
@NonNull String referenceKey,
boolean computeRetainedSize) {
...
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime)); // 沒有內存泄漏
}
// 如果有內存泄漏,查找內存泄漏的引用路徑,最終的分析實現LeakCanary是使用haha庫實現
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
...
}
}
HeapDumpListener.analyze()
其實就是啓動了一個前臺服務在其他進程分析內存泄漏的引用路徑。
3.5 LeakCanary源碼分析總結
在應用啓動時,通過 LeakCanary.install()
監聽內存泄漏,LeakCanary的處理過程如下:
-
構建
RetWatcher
提供內存泄漏分析前的相關參數(如DisplayService
通知服務,excludeRefs()
排除系統源碼泄漏),通過Application.registerXxxLifecycleCallback()
監聽Activity或Fragment生命週期轉發給RefWatcher
-
在Activity或Fragment回調
onDestroy()
時,監聽引用對象是否還在ReferenceQueue
中,有則表示內存泄漏,創建dump文件並通過Android工具Debug.dumpHprofData()
寫入內存泄漏數據,hprof文件將會在另一個前臺服務分析
4 LeakCanary 2.x(2.2)
LeakCanary
1.6.x和2.x有不同的一些地方:
-
2.x版本全部換爲使用kotlin編碼
-
內存泄漏引用路徑分析庫由
haha
庫替換爲shark
庫,據說減少了90%內存佔用,速度提升了6倍
其他的基本原理都和舊版本相同。
內部處理也有所不同:
- 不需要自己註冊
LeakCanary
如果不需要定製配置,新版本 LeakCanary
只需要引入依賴導入即可使用。不再需要在 Application.onCreate()
手動註冊 LeakCanary
,LeakCanary
自動通過 ContentProvider
註冊監聽(ContentProvider
會在應用啓動前創建):
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
InternalAppWatcher.install(application)
return true
}
}
RefWatcher.watch()
(kotlin不是RefWatcher
,爲了體現兩個版本的區別用這個名稱方便理解)新舊版本處理不同:
舊版本執行完GC後如果懷疑名單隊列中還有監聽的引用對象存在內存泄漏會直接dump並分析;在新版本中會存在一個閾值,如果內存泄漏的對象數量在閾值內是不會dump分析。
internal class HeapDumpTrigger(...) {
private fun checkRetainedObjects(reason: String) {
...
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 如果內存泄漏對象數量在閾值內,不生成dump文件分析
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
...
}
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int): Boolean {
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayRetainedObjectCount = retainedKeysCount
if (retainedKeydsCount == 0) {
...
return true
}
// 引發內存泄漏的對象在閾值內,如果應用在前臺會通知提示內存泄漏
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) { ... }
// 創建通知
showRetainedCountNotification(...)
...
return true
}
}
return false
}
}