2024年最全性能优化 记一次线上OOM问题处理_finalizerwatchdogdaemon(2),2024年最新HarmonyOS鸿蒙大厂技术面试题汇总

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

@Override public void runInternal() {
    // ...
    while (isRunning()) {
        try {
            // Use non-blocking poll to avoid FinalizerWatchdogDaemon communication when busy.
            FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
            // ...
            doFinalize(finalizingReference);
        } catch (InterruptedException ignored) {
        } catch (OutOfMemoryError ignored) {
        }
    }
}

}


在 `FinalizerDaemon.runInternal` 方法中会通过 FinalizerReference.queue(ReferenceQueue引用队列) 的 poll/remove 方法拿到 queue 中的 Reference 引用,接着执行 doFinalize() 方法,该方法会调用 Finalizer 对象的 finalize() 方法:



private void doFinalize(FinalizerReference<?> reference) {
FinalizerReference.remove(reference);
Object object = reference.get();
reference.clear();
try {
object.finalize();
} catch (Throwable ex) {
// The RI silently swallows these, but Android has always logged.
System.logE(“Uncaught exception thrown by finalizer”, ex);
} finally {
// Done finalizing, stop holding the object as live.
finalizingObject = null;
}
}


首先将获取到的 FinalizerReference 对象从 FinalizerReference.head 链表中移除,接着通过`reference.get()`方法得到 Java 对象,并执行其 finalize() 方法。


### 小结


**FinalizerReference 的主要作用是协助 FinalizerDaemon 守护线程来执行 Finalizer 对象的 finalize() 方法。**


* 在 f 类创建对象时,调用 `FinalizerReference.add()` 方法创建一个 FinalizerReference 对象(使用 ReferenceQueue 队列)并加入到 head 链表里。
* 在 f 类的对象被回收后,其对应的 FinalizerReference 对象会被加入到 ReferenceQueue 队列(FinalizerReference.queue)里。
* FinalizerDaemon 守护线程从 FinalizerReference.queue 队列中取出 FinalizerReference 对象,并执行其对应 f 类对象的 finalize() 方法。


## ReferenceQueueDaemon


上面看完了 FinalizerDaemon 守护线程,这里再看看 ReferenceQueueDaemon 守护线程,上面说到**在创建引用对象 Reference 的时候可以关联一个 ReferenceQueue 队列,当被引用对象被 gc 回收时,该 reference 对象就会被加入到其创建时关联的队列去,这个加入队列的操作就是由 ReferenceQueueDaemon 守护线程完成的**。



private static class ReferenceQueueDaemon extends Daemon {
@Override public void runInternal() {
while (isRunning()) {
Reference<?> list;
try {
synchronized (ReferenceQueue.class) {
if (ReferenceQueue.unenqueued == null) {
progressCounter.incrementAndGet();
do {
ReferenceQueue.class.wait();
} while (ReferenceQueue.unenqueued == null);
progressCounter.incrementAndGet();
}
list = ReferenceQueue.unenqueued;
ReferenceQueue.unenqueued = null;
}
} catch (InterruptedException e) {
continue;
} catch (OutOfMemoryError e) {
continue;
}
ReferenceQueue.enqueuePending(list, progressCounter);
}
}
}


具体源码感兴趣的话可以自己去看看,这里就不贴太多源码了。


## FinalizerWatchdogDaemon


这里再补充一下 FinalizerWatchdogDaemon 守护线程,它跟 FinalizerDaemon 以及 ReferenceQueueDaemon 线程是一起 start 的。我们再看一下上面 FinalizerDaemon 和 ReferenceQueueDaemon 线程的 runInternal() 方法:



// ReferenceQueueDaemon
@Override public void runInternal() {
FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
while (isRunning()) {
Reference<?> list;
try {
synchronized (ReferenceQueue.class) {
if (ReferenceQueue.unenqueued == null) {
progressCounter.incrementAndGet();
FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
do {
ReferenceQueue.class.wait();
} while (ReferenceQueue.unenqueued == null);
progressCounter.incrementAndGet();
FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);
}
list = ReferenceQueue.unenqueued;
ReferenceQueue.unenqueued = null;
}
} catch (InterruptedException e) {
continue;
} catch (OutOfMemoryError e) {
continue;
}
ReferenceQueue.enqueuePending(list, progressCounter);
FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();
}
}

// FinalizerDaemon
@Override public void runInternal() {
FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);
while (isRunning()) {
try {
FinalizerReference<?> finalizingReference = (FinalizerReference<?>)queue.poll();
if (finalizingReference != null) {
finalizingObject = finalizingReference.get();
} else {
finalizingObject = null;
FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);
finalizingReference = (FinalizerReference<?>)queue.remove();
finalizingObject = finalizingReference.get();
FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);
}
doFinalize(finalizingReference);
} catch (InterruptedException ignored) {
} catch (OutOfMemoryError ignored) {
}
}
}


上面的 monitoringNotNeeded 方法会休眠线程,停止 timeout 计时,而 monitoringNotNeeded 会唤醒 FinalizerWatchdogDaemon 守护线程。看看 FinalizerWatchdogDaemon 的源码,我加了一些注释:



private static class FinalizerWatchdogDaemon extends Daemon {
// Single bit values to identify daemon to be watched.
// 监控的两种类型
static final int FINALIZER_DAEMON = 1;
static final int RQ_DAEMON = 2;

@Override public void runInternal() {
    while (isRunning()) {
        // sleepUntilNeeded 内部会调用 wait() 方法,不贴了
        if (!sleepUntilNeeded()) {
            continue;
        }
        final TimeoutException exception = waitForProgress();
        if (exception != null && !VMDebug.isDebuggerConnected()) {
            // 抛出异常 Thread.currentThread().dispatchUncaughtException(exception)
            timedOut(exception);
            break;
        }
    }
}

// 去掉 whichDaemon 的监控,进入 wait
private synchronized void monitoringNotNeeded(int whichDaemon) {
    activeWatchees &= ~whichDaemon;
}

// 添加 whichDaemon 的监控, notify 线程
private synchronized void monitoringNeeded(int whichDaemon) {
    int oldWatchees = activeWatchees;
    activeWatchees |= whichDaemon;
    if (oldWatchees == 0) {
        // 调用 notify 唤醒
        notify();
    }
}

// 判断是否添加了指定 whichDaemon 类型的监控
private synchronized boolean isActive(int whichDaemon) {
    return (activeWatchees & whichDaemon) != 0;
}

// 调用 isActive 判断开启监控的类型,通过 Thread.sleep() 方法休眠指定时间
// 休眠结束后返回 TimeoutException 异常,若途中监控的逻辑执行完成则表示未超时,返回 null
// 等待时间: VMRuntime.getFinalizerTimeoutMs
// If the FinalizerDaemon took essentially the whole time processing a single reference,
// or the ReferenceQueueDaemon failed to make visible progress during that time, return an exception.
private TimeoutException waitForProgress() {
    finalizerTimeoutNs = NANOS_PER_MILLI * VMRuntime.getRuntime().getFinalizerTimeoutMs();
    // ...
}

}


上面一些细节代码就不贴出了, FinalizerWatchdogDaemon 主要用来监控两种类型的执行时长: FINALIZER\_DAEMON 和 RQ\_DAEMON。当执行超时时就会抛出 TimeOutException 异常,因此不要在 finalize() 方法中做耗时操作。


这个超时时间在 AOSP 中定义为 10s, 国内厂商可能会修改这个值, 据说有的改为了 60 s, 有兴趣的同学可以自己解包看看:



RUNTIME_OPTIONS_KEY (unsigned int, FinalizerTimeoutMs, 10000u)


## OOM排查


通过线上 hprof 文件排除了大对象和内存泄漏的可能,接着在 hprof 中发现存在大量的 X(用 X 表示业务上的某个对象) 对象堆积,这些对象对应的 Java 类是一个与 Native 层有关的类,其重写了 finalize() 方法,线下无法复现堆积大量 X 对象的路径。



> 
> **可能有某个业务场景的代码逻辑使用不当造成了 X 对象的疯狂创建,导致 FinalizerDaemon 线程来不及回收**
> 
> 
> 


由于这个 X 对象的创建场景比较多,因此无法通过代码 review 来定位到有问题的创建代码,采取了一些监控手段后依旧无果。



> 
> **用治标不治本的方式,显式调用系统 gc 和 runFinalization 方法**
> 
> 
> 


在定位不到具体的问题场景后,因为猜测是创建了大量的 X 对象导致 FinalizerDaemon 线程回收不及时,因此分别在主线程和子线程中显式调用系统 gc 和 runFinalization 方法来手动回收 X 对象。


结果:子线程调用无效果, OOM 未下降;主线程调用发生了 ANR。


查看 ANR 堆栈后,发现 ANR 的位置都在另外一个 f 对象的 finalize() 方法中调用到的一行 Native 代码,因此问题就明了了,是因为在某个 finalize() 方法中调用到的这行代码卡死了(逻辑问题导致死锁),因此阻塞了 FinalizerDaemon 线程的执行,进而导致对象堆积。另外本应发生的 TimeoutException 异常被我们的异常处理框架捕获了,因此未曾暴露出来。


## 总结


Java 中并没有析构函数, finalize() 实现了类似析构函数的概念,可以在对象被回收前做一些回收性的操作,在 JNI 开发中可能被用来进行 Native 内存的释放。关于 f 类使用不当会造成的影响:


* FinalizerDaemon 守护线程的优先级不高,在 CPU 紧张的情况下调度可能会被影响,所以可能无法及时回收 f 类对象。
* finalize 对象由于 finalize() 的引用,它变成了一个强引用,即使没有其他显式的强引用了,它也还是无法立即被回收。
* 因此 f 对象至少需要 2 次 gc 才可能被回收,第一次 gc 时会将 f 对象加入到 ReferenceQueue 中,而等到 finalize() 方法执行完毕后的下一次 gc 里才有可能回收它。由于守护线程的优先级低,这期间可能已经发生过多次 gc 了,长时间未回收的话又可能会导致 f 类在资源紧张时进入到老年代,从而引起老年代的 gc 甚至是 full gc。


因此尽量不要重载 finalize() 方法,而是通过一些逻辑上的接口去释放内存,如果一定要重载它的话,也不要让一些需要频繁创建的对象或者大对象去通过 finalize() 释放,不然总有一天会出现问题的。


与此相关的守护线程有四个,感兴趣的同学可以深入看看相关源码:



// libcore/libart/src/main/java/java/lang/Daemons.java
private static final Daemon[] DAEMONS = new Daemon[] {
HeapTaskDaemon.INSTANCE,
ReferenceQueueDaemon.INSTANCE,
FinalizerDaemon.INSTANCE,
FinalizerWatchdogDaemon.INSTANCE,
};



> 
> 作者:苍耳叔叔  
>  链接:https://juejin.cn/post/7136063993537363975
> 
> 
> 


## 最后


如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。


如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/06e41b3932164f0db07014d54e6e5626.png)  
 相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。


#### 一、架构师筑基必备技能


1、深入理解Java泛型  
 2、注解深入浅出  
 3、并发编程  
 4、数据传输与序列化  
 5、Java虚拟机原理  
 6、高效IO  
 ……


![在这里插入图片描述](https://img-blog.csdnimg.cn/079bc2315e7e4f73b8fe703c3c51ae8d.png)


#### 二、Android百大框架源码解析


1.Retrofit 2.0源码解析  
 2.Okhttp3源码解析  
 3.ButterKnife源码解析  
 4.MPAndroidChart 源码解析  
 5.Glide源码解析  
 6.Leakcanary 源码解析  
 7.Universal-lmage-Loader源码解析  
 8.EventBus 3.0源码解析  
 9.zxing源码分析  
 10.Picasso源码解析  
 11.LottieAndroid使用详解及源码解析  
 12.Fresco 源码分析——图片加载流程


![在这里插入图片描述](https://img-blog.csdnimg.cn/2206daa6ec0b4bdfb0ea7a908d1249e4.png)


#### 三、Android性能优化实战解析


* 腾讯Bugly:对字符串匹配算法的一点理解
* 爱奇艺:安卓APP崩溃捕获方案——xCrash
* 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
* 百度APP技术:Android H5首屏优化实践
* 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
* 携程:从智行 Android 项目看组件化架构实践
* 网易新闻构建优化:如何让你的构建速度“势如闪电”?
* …


![在这里插入图片描述](https://img-blog.csdnimg.cn/2139f00c8fc74031b9fd38257c96c22e.png)


#### 四、高级kotlin强化实战


1、Kotlin入门教程  
 2、Kotlin 实战避坑指南  
 3、项目实战《Kotlin Jetpack 实战》


* 从一个膜拜大神的 Demo 开始
* Kotlin 写 Gradle 脚本是一种什么体验?
* Kotlin 编程的三重境界
* Kotlin 高阶函数
* Kotlin 泛型
* Kotlin 扩展
* Kotlin 委托
* 协程“不为人知”的调试技巧
* 图解协程:suspend


![在这里插入图片描述](https://img-blog.csdnimg.cn/d05bd9ae3a9e481fa317022bfe161c7d.png)


#### 五、Android高级UI开源框架进阶解密


1.SmartRefreshLayout的使用  


**深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

![](https://img-blog.csdnimg.cn/direct/743b668910224b259a5ffe804fa6d0db.png)
![img](https://img-blog.csdnimg.cn/img_convert/0cf1082b65ca9db312cb624aae02790d.png)
![img](https://img-blog.csdnimg.cn/img_convert/422c7be9dc1caf7aae16f96cd236484e.png)

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

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**

的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

![](https://img-blog.csdnimg.cn/direct/743b668910224b259a5ffe804fa6d0db.png)
[外链图片转存中...(img-OCn46ssN-1715659205018)]
[外链图片转存中...(img-lpayOnRR-1715659205019)]

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

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值