Java内存问题 及 LeakCanary 原理分析(1)

在JDK1.2之后,为了优化内存的利用及GC的效率,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用4种。

1、强引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

2、软引用,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。SoftReference表示软引用。

3、弱引用,只要有GC,无论当前内存是否足够,都会回收掉_只_被弱引用关联的对象。WeakReference表示弱引用。

4、虚引用,这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系。PhantomReference表示虚引用,需要搭配ReferenceQueue使用,检测对象回收情况。

关于JVM内存管理的一些建议

1、尽可能的手动将无用对象置为null,加快内存回收。 2、可考虑对象池技术生成可重用的对象,较少对象的生成。 3、合理利用四种引用。

三、内存泄漏

持有一个生命周期较短的引用时或内部的子模块对象的生命周期超过了外面模块的生命周期,即本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。

内存泄漏是造成应用程序OOM的主要原因之一,尤其在像安卓这样的移动平台,难免会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出Error。

安卓平台常见的内存泄漏

1、静态成员变量持有外部(短周期临时)对象引用。 如单例类(类内部静态属性)持有一个activity(或其他短周期对象)引用时,导致被持有的对象内存无法释放。

2、内部类。当内部类与外部类生命周期不一致时,就会造成内存泄漏。如非静态内部类创建静态实例、Activity中的Handler或Thread等。

3、资源没有及时关闭。如数据库、IO流、Bitmap、注册的相关服务、webview、动画等。

4、集合内部Item没有置空。

5、方法块内不使用的对象,没有及时置空。

四、如何检测内存泄漏

Android Studio供了许多对App性能分析的工具,可以方便分析App性能。我们可以使用Memory Monitor和Heap Dump来观察内存的使用情况、使用Allocation Tracker来跟踪内存分配的情况,也可以通过这些工具来找到疑似发生内存泄漏的位置。

堆存储文件(hpof)可以使用DDMS或者Memory Monitor来生成,输出的文件格式为hpof,而MAT(Memory Analysis Tool)就是来分析堆存储文件的。

然而MAT工具分析内存问题并不是一件容易的事情,需要一定的经验区做引用链的分析,需要一定的门槛。 随着安卓技术生态的发展,LeakCanary 开源项目诞生了,只要几行代码引入目标项目,就可以自动分析hpof文件,把内存泄漏的地方展示出来。

五、LeakCanary原理解析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

A small leak will sink a great ship.

LeakCanary内存检测工具是由squar公司开源的著名项目,这里主要分析下源码实现原理。

基本原理

主要是在Activity的&onDestroy方法中,手动调用 GC,然后利用ReferenceQueue+WeakReference,来判断是否有释放不掉的引用,然后结合dump memory的hpof文件, 用HaHa分析出泄漏地方。

源码分析

LeakCanary集成很方便,只要几行代码,所以可以从入口跟踪代码,分析原理

if (!LeakCanary.isInAnalyzerProcess(WeiboApplication.this)) {
LeakCanary.install(WeiboApplication.this);
}

public static RefWatcher install(Application application) {
return ((AndroidRefWatcherBuilder)refWatcher(application)
.listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()))//配置监听器及分析数据格式
.buildAndInstall();
}

从这里可看出,LeakCanary会单独开一进程,用来执行分析任务,和监听任务分开处理。

方法_install_中主要是构造来一个RefWatcher,

public RefWatcher buildAndInstall() {
RefWatcher refWatcher = this.build();
if(refWatcher != RefWatcher.DISABLED) {
LeakCanary.enableDisplayLeakActivity(this.context);
ActivityRefWatcher.install((Application)this.context, refWatcher);
}

return refWatcher;
}

public static void install(Application application, RefWatcher refWatcher) {
(new ActivityRefWatcher(application, refWatcher)).watchActivities();
}

private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
public void onActivityStarted(Activity activity) {}
public void onActivityResumed(Activity activity) {}
public void onActivityPaused(Activity activity) {}
public void onActivityStopped(Activity activity) { }
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}

public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};

void onActivityDestroyed(Activity activity) {
this.refWatcher.watch(activity);
}

具体监听的原理在于 Application 的_registerActivityLifecycleCallbacks_方法,该方法可以对应用内所有 Activity 的生命周期做监听, LeakCanary只监听了Destroy方法。

在每个Activity的OnDestroy()方法中都会回调refWatcher.watch()方法,那我们找到的RefWatcher的实现类,看看具体做什么。

public void watch(Object watchedReference, String referenceName) {
if(this != DISABLED) {
Preconditions.checkNotNull(watchedReference, “watchedReference”);
Preconditions.checkNotNull(referenceName, “referenceName”);
long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();//保证key的唯一性
this.retainedKeys.add(key);
KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
this.ensureGoneAsync(watchStartNanoTime, reference);
}
}

final class KeyedWeakReference extends WeakReference {
public final String key;
public final String name;

KeyedWeakReference(Object referent, String key, String name, ReferenceQueue referenceQueue) {//ReferenceQueue类监听回收情况
super(Preconditions.checkNotNull(referent, “referent”), (ReferenceQueue)Preconditions.checkNotNull(referenceQueue, “referenceQueue”));
this.key = (String)Preconditions.checkNotNull(key, “key”);
this.name = (String)Preconditions.checkNotNull(name, “name”);
}
}

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
this.watchExecutor.execute(new Retryable() {
public Result run() {
return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
}
});
}

_KeyedWeakReference_是WeakReference类的子类,用了 KeyedWeakReference(referent, key, name, ReferenceQueue )的构造方法,将监听的对象(activity)引用传递进来,并且New出一个ReferenceQueue来监听GC后 的回收情况。

以下代码ensureGone()方法就是LeakCanary进行检测回收的核心代码:

Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
this.removeWeaklyReachableReferences();//先将引用尝试从队列中poll出来
if(this.debuggerControl.isDebuggerAttached()) {//规避调试模式
return Result.RETRY;
} else if(this.gone(reference)) {//检测是否已经回收
return Result.DONE;
} else {
//如果没有被回收,则手动GC
this.gcTrigger.runGc();//手动GC方法
this.removeWeaklyReachableReferences();//再次尝试poll,检测是否被回收
if(!this.gone(reference)) {
// 还没有被回收,则dump堆信息,调起分析进程进行分析
long startDumpHeap = System.nanoTime();
long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = this.heapDumper.dumpHeap();
if(heapDumpFile == HeapDumper.RETRY_LATER) {
return Result.RETRY;//需要重试
}

long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
}

return Result.DONE;
}
}

private boolean gone(KeyedWeakReference reference) {
return !this.retainedKeys.contains(reference.key);
}

private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
this.retainedKeys.remove(ref.key);
}
}

方法ensureGone中通过检测referenceQueue队列中的引用情况,来判断回收情况,通过手动GC来进一步确认回收情况。 整个过程肯定是个耗时卡UI的,整个过程会在WatchExecutor中执行的,那WatchExecutor又是在哪里执行的呢?

LeakCanary已经利用Looper机制做了一定优化,利用主线程空闲的时候执行检测任务,这里找到WatchExecutor的实现类,研究下原理:

public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = “LeakCanary-Heap-Dump”;
private final Handler mainHandler = new Handler(Looper.getMainLooper());
private final Handler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;

public AndroidWatchExecutor(long initialDelayMillis) {
HandlerThread handlerThread = new HandlerThread(“LeakCanary-Heap-Dump”);
handlerThread.start();
this.backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
this.maxBackoffFactor = 9223372036854775807L / initialDelayMillis;
}

public void execute(Retryable retryable) {
if(Looper.getMainLooper().getThread() == Thread.currentThread()) {
this.waitForIdle(retryable, 0);//需要在主线程中检测
} else {
this.postWaitForIdle(retryable, 0);//post到主线程
}

}

void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
this.mainHandler.post(new Runnable() {
public void run() {
AndroidWatchExecutor.this.waitForIdle(retryable, failedAttempts);
}
});
}

void waitForIdle(final Retryable retryable, final int failedAttempts) {
Looper.myQueue().addIdleHandler(new IdleHandler() {
public boolean queueIdle() {
AndroidWatchExecutor.this.postToBackgroundWithDelay(retryable, failedAttempts);//切换到子线程
return false;
}
});
}

void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long)Math.min(Math.pow(2.0D, (double)failedAttempts), (double)this.maxBackoffFactor);
long delayMillis = this.initialDelayMillis * exponentialBackoffFactor;
this.backgroundHandler.postDelayed(new Runnable() {
public void run() {
Result result = retryable.run();//RefWatcher.this.ensureGone(reference, watchStartNanoTime)执行
if(result == Result.RETRY) {
AndroidWatchExecutor.this.postWaitForIdle(retryable, failedAttempts + 1);
}

}
}, delayMillis);
}
}

这里用到了Handler相关知识,Looper中的MessageQueue有个_mIdleHandlers_队列,在获取下个要执行的Message时,如果没有发现可执行的下个Msg,就会回调queueIdle()方法。

Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;😉 {
···
···//省略部分消息查找代码

if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
···

return msg;
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

写在最后

可能有人会问我为什么愿意去花时间帮助大家实现求职梦想,因为我一直坚信时间是可以复制的。我牺牲了自己的大概十个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

复习一周,字节跳动三场技术面+HR面,不小心拿了offer

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
个小时写了这片文章,换来的是成千上万的求职者节约几天甚至几周时间浪费在无用的资源上。

[外链图片转存中…(img-qNLWuw0N-1713130754578)]

[外链图片转存中…(img-Qf7e33av-1713130754578)]

上面的这些(算法与数据结构)+(Java多线程学习手册)+(计算机网络顶级教程)等学习资源
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值