Android内存优化(一)之FinalizerDaemon和FinalizerWatchDog多线程内存泄露案例

【安卓源码】JVM GC超时分析 在object.finalize()超时10秒后,(最多不大于20s),会触发GC超时关闭JVM。 阅读详情
AI摘要

本文分析了一起与多线程相关的内存泄露问题,详细解释了FinalizerDaemon和FinalizerWatchdogDaemon的工作原理,探讨了oldFinalizingObject字段导致内存泄露的原因及解决方案。

前期有一个内存泄露case跟多线程相关,简单记录如下:

问题描述

跑一晚上的内存测试后,会出现很多的内存泄露,泄露trace如下

In *********:2.0.0:2.
* ************.editor.photo.app.PhotoEditor has leaked:
* GC ROOT static java.lang.Daemons$FinalizerDaemon.INSTANCE
* references java.lang.Daemons$FinalizerDaemon.oldFinalizingObject
* references android.view.RenderNode.mOwningView
* references android.view.View.mContext
* leaks *************.editor.photo.app.PhotoEditor instance
* Retaining: 23 MB.
* Reference Key: 62e93630-d8b7-4fda-9522-9e295e16d739
* Device: Xiaomi Xiaomi MI 5s Plus natrium
* Android Version: 7.0 API: 24
* Durations: watch=5082ms, gc=164ms, heap dump=2349ms, analysis=37914ms

从上述trace看,泄露都可达到23MB,已经是非常恐怖的泄露问题

分析过程

初步分析

最初怀疑跟object的finalize执行相关,FinalizerDaemon是用来gc的daemon thread,怀疑dump的时候,这个线程正好引用了被监控对象
具体解释如下:
实现了object的finalize()的类在创建时会新建一个FinalizerReference,这个对象是强引用类型,封装了override finalize()的对象,下面直接叫原对象。原对象没有被其他对象引用时(FinalizeReference除外),执行GC不会马上被清除掉,而是放入一个静态链表中(ReferenceQueue),有一个守护线程专门去维护这个链表,如何维护呢?就是轮到该线程执行时就弹出里面的对象,执行它们的finalize(),对应的FinalizerReference对象在下次执行GC时就会被清理掉。
一个堆的FinalizerReference会组成一条双向链表,垃圾回收器应该会持有链表头(链表头在FinalizerReference中为一个静态成员)。
为什么会泄漏:
直接原因就是守护线程优先级比较低,运行的时间比较少。如果较短时间内创建较多的原对象,就会因为守护线程来不及弹出原对象而使FinalizerReference和原对象都得不到回收。无论怎样调用GC都没有用的,因为只要原对象没有被守护线程弹出执行其finalize()方法,FinalizerReference对象就不会被GC回收。
但此种情况我们不可认为其为泄露,进程中还有一个Daemon线程FinalizerWatchdogDaemon,如果拥有finalize的object超过10s(MIUI改成了60s)没有被回收,则发生crash,但leakcanary的watch时间在5秒左右,也就是说,FinalizerDaemon线程引用被监控对象,在系统中是正常现象~跟Leakcanary提了一个issue,https://github.com/square/leakcanary/issues/856

深度分析

但看了下leakcanary源码,发现FinalizerDaemon是用来gc的daemon thread,怀疑dump的时候,这个线程正好引用了被监控对象这种情况应该是不存在的,解释如下
按照我们的假设,FinalizerDaemon.INSTANCE的finalizingObject成员持有被监控对象的引用,很有可能FinalizerDaemon.INSTANCE作为GC ROOT,泄露路径基本为

* GC ROOT static FinalizerDaemon.INSTANCE

* references java.lang.Daemons$FinalizerDaemon.finalizingObject

* references ********(被监控对象)

但看了下GCTrigger,有一句:
System.runFinalization();

看了下执行流:

System.runFinalization->Runtime.runFinalization()->VMRuntime.runFinalization->FinalizerReference.finalizeAllEnqueued 
// Alloate a new sentinel, this creates a FinalizerReference.
Sentinel sentinel;
// Keep looping until we safely enqueue our sentinel FinalizerReference.
// This is done to prevent races where the GC updates the pendingNext
// before we get the chance.
do {
sentinel = new Sentinel();
} while (!enqueueSentinelReference(sentinel));
sentinel.awaitFinalization(timeout);
// Alloate a new sentinel, this creates a FinalizerReference.
Sentinel sentinel;
// Keep looping until we safely enqueue our sentinel FinalizerReference.
// This is done to prevent races where the GC updates the pendingNext
// before we get the chance.
do {
    sentinel = new Sentinel();
} while (!enqueueSentinelReference(sentinel));
sentinel.awaitFinalization(timeout);

在FinalizerReference中会等待队列的元素执行完finalize方法,这样的话,我们假设的场景在Leakcanary工具运行的过程中是不存在的,于是把提过的issue关闭了,排除不是系统问题,也不是工具问题,而是我们的代码出了问题~
泄露真正原因
从深度分析中可以看出,object的finalize方法的执行不会被工具检测为内存泄露,即使出现泄露,泄露元素也应该是
* references java.lang.Daemons$FinalizerDaemon.finalizingObject,而不是* references java.lang.Daemons$FinalizerDaemon.oldFinalizingObject,慢慢的真正的凶手走入视野~

commit 1423bd1d0ad5aefa4a7b19c975ff5ab3a7ed9eff
Author: yuanhuihui <****@****.com>
Date:   Mon Feb 20 17:49:03 2017 +0800
    adjust finnalizer watchdog

    1. It's not foreground process, no need to throw exception when finalizer exceed 10s.
    2. add finnaliing Object checker, whether occur timeout with different finalizing Object.

    Change-Id: I57083da6ae6bd6c358b88e6933a70e86109be299
    Signed-off-by: ****** 

先说下我对于FinalizerDaemon.oldFinalizingObject引起的内存泄露的理解
1:为什么出现内存泄露
《1》大概流程:
系统的许多工具类实现了object的finalize方法,这些类在创建时会新建FinalizerRefernece,封装override finalize方法的对象,是个强引用关系,GC时会将FinalizerReference放入静态链表ReferenceQueue(双向链表),FinalizerDaemon线程维护此链表,当线程获得执行资源时,从队列中弹出里面的FinalizerReference对象,并执行封装的object的finalize方法,而FinalizerWatchdogDeamon线程会监控 FinalizerDaemon线程的执行object.finalize()方法的快慢问题,如果ReferenceQueue是空的,FinalizerWatchdogDeamon线程会睡去,直到ReferenceQueue不为空,FinalizeDaemon线程会去唤醒FinalizerWatchdogDeamon,告诉它有工作来了
《2》泄露出现的场景
FinalizerReference

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;
}

FinalizerDaemon线程的执行过程是OK的,引用关系清理了之后a就可被GC了,但还有一个关键:FinalizerWatchdog线程的waitForFinalization方法中,会将oldFinalizingObject赋值
// MIUI ADD:
FinalizerDaemon.INSTANCE.oldFinalizingObject = FinalizerDaemon.INSTANCE.finalizingObject;
这是个多线程的问题,此时finalizingObject的值不确定,很可能为null,很可能为指向对象a的引用,如果finalizingObject为null,oldFinalizingObject不会持有任何对象的引用,不会出现内存泄露问题,如果finalizingObject为指向对象a的引用,这句话会导致a的引用计数再+1,即使FinalizerDaemon线程执行完了doFinalize方法,也无法被回收,因为oldFinalizingObject依然持有对象a的引用,此时内存泄露就出现了,但如果下一次赋值oldFinalizingObject = finalizingObject发生了,也会导致a的引用计数-1,此时a就可正常被GC掉,泄露又消失了。
因此出现内存泄露的时间窗口:FinalizerDaemon线程先赋值finalizingObject,然后FinalizerWatchdogDaemon线程的waitForFinalization再执行,而且oldFinalizingObject = finalizingObject为最后一次赋值,最后FinalizerDaemon的doFInalize方法再执行 finalizingObject = null; ,此时就会出现内存泄露
因此此内存泄露是个偶现问题,而且比较隐蔽,也不会像FC,ANR那样很容易被发现,引发的内存泄露很可能会在后面的操作中,又被释放了
2:解决方法
<1>觉引入oldFinalizingObject是为了更好的debug,发现finalize超时问题的细节,但这样带来了内存问题,如果此change只是为了debug用,建议revert掉
<2>如果不能revert,感觉可在FinalizerDaemon线程的run方法中,在让FinalizerWatchdogDaemon线程睡去前,把oldFinalizingObject置为null,这样即使oldFinalizingObject = finalizingObject为最后一次赋值,ReferenceQueue为空时,将oldFinalizingObject置为null,会使内存里的对象引用计数恢复正常,可被正常GC掉

progressCounter.lazySet(++localProgressCounter);
// Slow path; block.
FinalizerWatchdogDaemon.INSTANCE.goToSleep();
finalizingReference = (FinalizerReference<?>)queue.remove();
finalizingObject = finalizingReference.get();
progressCounter.set(++localProgressCounter);
FinalizerWatchdogDaemon.INSTANCE.wakeUp();

问题修复

经好袁老师沟通后,决定将此change revert掉修复此问题~问题解决~

突然弹出王者荣耀停止运行,GC超时导致的后台应用崩溃问题分析 阅读详情

相关推荐

Codesys电子凸轮功能块MC_CamIn的masteroffset和slaveoffset参数的使用

问题1:在Codesys使用作轴电子凸轮时,MC_CamTableSelect的参数设置为MasterAbsolute=1,SlaveAbsolute=1,此设置为绝得模式, 此时主从轴当前位置都不为零时,此时Masteroffset=0、slaveOffset=0,凸轮启时出现突变,实轴无法启动。问题分析:在觉得模式下,主轴位置和从轴为是对应凸轮表的相位的,Masteroffset=0、slaveOffset=0,启动点时在主轴位置=0,从轴位置=0的点启动,此时主轴当前位置不为零则主从轴位置发生突变,

weixin_43587371的博客 1801

Java的Finalizer引发的内存溢出

本文介绍的是Java里一个内建的概念,Finalizer。你可能对它对数家珍,但也可能从未听闻过,这得看你有没有花时间完整地看过一遍java.lang.Object类了。在java.lang.Object里面就有一个finalize()的方法。这个方法的实现是空的,不过一旦实现了这个方法,就会触发JVM的内部行为,威力和危险并存。 如果JVM发现某个类实现了finalize()方法的话,那么见证奇...

ScutKevinYzy的博客 616

告别调参玄学:用 DINOv2 (ViT-S/ViT-G) 和 PCA 可视化,直观理解自监督特征

本文深入解析了DINOv2(ViT-S/ViT-G)模型在自监督学习中的特征提取机制,通过PCA降维技术实现特征空间的可视化。文章对比了不同规模模型的特征表达差异,并提供了PCA阈值调优和深度分析的实用技巧,帮助读者直观理解自监督学习的视觉密码。

weixin_42565865的博客 409

JVM 系列(6)吊打面试官:为什么 finalize() 方法只会执行一次?

Java Finalizer 机制提供了一个在对象被回收之前释放占用资源的时机,但是都说 Finalizer 机制是不稳定且危险的,不推荐使用,这是为什么呢?今天我们来深入理解这个问题。

pengxurui的博客 831

ROS2+MoveIt2+Gazebo仿真实战:从零搭建六轴机械臂控制闭环

本文详细介绍了如何从零搭建一个完整的ROS2+MoveIt2+Gazebo六轴机械臂仿真控制闭环。通过手写URDF模型、配置MoveIt2运动规划、集成Gazebo物理仿真与ROS2 Control控制器,最终实现RViz中规划轨迹、Gazebo中同步执行的完整数字孪生验证平台,为机器人算法开发和硬件验证提供低成本、高效的仿真解决方案。

weixin_29093017的博客 484

FOFA奇技淫巧:从搜索语法到实战场景的深度挖掘

本文深入探讨了FOFA搜索语法在网络空间测绘中的高效应用,从基础语法到实战场景的深度挖掘。通过渗透测试、威胁狩猎、资产发现等实际案例,展示了如何利用FOFA精准定位目标系统、识别蜜罐、追踪威胁,并提供了合规使用指南和性能优化技巧,助力安全工作者提升效率。

weixin_32428571的博客 235

finalize() timed out after 10 seconds的解决方案

finalize() timed out after 10 seconds的解决方案 2020-06-022020-06-02 15:32:08阅读 1.1K0最近项目的bugly报了一个错finalize() timed out after 10 seconds。最初遇到这个问题,本人一脸懵逼。没写过这个方法怎么会在这里面报错的?查阅了网上的资料才发现,通常这个错误发生在 java.lang.Daemons$FinalizerDaemon.doFinalize的方法中,直接原因是对象的 finalize(

m0_50266789的博客 1678

Claude Code、Codex、Gemini CLI、OpenCode 和 OpenClaw 配置管理太麻烦?试试这个统一管理工具

还在为 Claude Code、Codex、Gemini CLI 等 AI CLI 配置烦恼?🤯 这款桌面工具支持五大助手统一管理,一键切换 50+ 服务商,MCP 和 Skills 集中管理 🛠️,跨平台支持,让你的 AI 编程更高效 ⚡

XiaoqiangClub的博客 913

AndroidRuntime: FATAL EXCEPTION: FinalizerWatchdogDaemon问题解决

MainActivity有一个按钮,点按钮进入到CameraActivity,获取结果图片后返回MainActivity,然后联网发送结果图片并返回json结果。第一次点按钮进入CameraActivity并返回MainActivity结果的时候正常,第二次点按钮进入CameraActivity返回结果,一切正常,然后在MainActivity界面不动,等过了30秒(华为手机30秒,小米手机10秒

HelloWorld的专栏 1万+

YOLOv11训练:Roboflow免费数据集获取与格式转换超详细教程

公共数据集库:包含超过20,000个开源数据集数据标注工具:支持在线标注和团队协作数据预处理:自动增强、扩充和格式化版本控制:像管理代码一样管理数据集版本多格式导出:支持所有主流目标检测格式import cv2# 加载图像# 加载标注# 数据增强# 调整尺寸# 调整标注坐标# 转换为Tensor# 实现保持长宽比的resize逻辑pass# 坐标转换逻辑pass# 数据增强实现pass数据集选择优先选择Roboflow上标注质量高的官方数据集。

weixin_39818775的博客 2360

OceanSim: 基于Isaac Sim GPU 加速水下机器人感知仿真框架

OceanSim 采用先进的基于物理的渲染技术,对水下视觉和声学传感器进行精确建模。与其他模拟引擎相比,OceanSim 的渲染速度要快得多。作为 NVIDIA Issac Sim 的扩展,OceanSim 采用了多功能设计。通过融入快速发展的 NVIDIA Omniverse 生态系统,我们的模拟器自然而然地在机器人学习和水下机器人技术之间架起了一座桥梁,从而促进了水下应用中体现型人工智能技术的开发和部署。OceanSim 将以开放源码的方式提供,使研究界能够以合作的模式作出贡献。

机器人控制与仿真应用 2050

java.util.concurrent.TimeoutException

摘要: 一旦FinalizerDaemon在处理一个对象的时候超过10s中,那么就会结束进程,导致崩溃 背景: Finalize对象累积太多,导致FinalizerDaemon线程来不及处理:可能项目中没有类重写finalize方法,但Android系统中有非常多的类实现了finalize方法,如:Binder,View,Matrix,Bitmap,AssetManager等 运行过程中息屏,或者某些省电模式下,系统CPU降频,FinalizerDaemon线程获得时间片短且执行速度慢导致超时 某个析构函

云梦九章~ 2万+

2025年7月5日全国青少年信息素养大赛图形化(Scratch)编程小学低年级组复赛真题+答案

2025年7月5日全国青少年信息素养大赛图形化(Scratch)编程小学低年级组复赛真题+答案

伶俐角少儿编程 898

Android踩坑经验-Finalize TimeoutException原因及解决方案

调用栈信息: 背景知识:FinalizerDamemon和FinalizerWatchdogDaemon FinalizerDamemon 析构守护线程,重写了finalize的对象,在创建时会新建一个FinalizerReference,这个对象是强引用类型,封装了override finalize()的对象,下面直接叫原对象。原对象没有被其他对象引用时(FinalizeReference除外...

longlong2015的博客 3562

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Android系统问题及日志分析

android 系统问题及日志分析

QG 6713

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

【安卓源码阅读】简单追溯几个内存回收的守护线程

其实在进行这部分代码的追溯之前,我也没想到会追溯到runFinalization方法。但是工作中经常需要在各个代码块中加逻辑,有时候加了一些逻辑之后,顺便解决一下之前工作中的困惑,也是挺好的。最后提一点,网上有人喜欢在runFinalization之前先调用一下gc方法,其实没有必要,在runFinalization方法的内部,会帮你gc了。...

qq_23542165的博客 1445

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

异常 FinalizerWatchdogDaemon

FinalizerWatchdogDaemon(1550) java.util.concurrent.TimeoutException android.view.ThreadedRenderer.finalize() timed out after 10 seconds 1android.view.ThreadedRenderer.nDeleteProxy(Native Me...

u012886265的专栏 1532

性能优化 | 记一次线上OOM问题处理

现在开始看看 FinalizerReference 是个啥玩意,知己知彼,才能知道怎么去修它。这个 FinalizerReference 是用来协助 FinalizerDaemon 线程处理对象的 finalize() 工作的,一次性出现了三个名词,我们先从 FinalizerReference 看起。> prev;> next;if (head!= null) {......

大模型训练 619

再谈Finalizer对象--大型App中内存与性能的隐性杀手

在上一篇《提升Android下内存的使用意识和排查能力》的文章中,多次提到了Finalizer对象。也可以看到该对象的清理至少是需要两次GC才能完成,而在Android5.0,尤其是6.0以后的系统中,对于该对象的回收变得更加的慢。我们在开发的时候往往关注内存的分配、泄漏,却容易忽视Finalizer对象,其实在大型App中,该对象是引起内存和性...

weixin_34114823的博客 675

从Daemons到finalize timed out after 10 seconds

〇.序起因,在Fabric遇到了很多BUG..出于想对于BUG的来龙去脉做进一步的了解.做了一番调查.本文也是Android Crash的防护与追踪的后续研究. 这是Fabric给出的一些link 传送门 : How to handle :java.util.concurrent.TimeoutException: android.os.BinderProxy.finalize() ti

Android 是一种内伤 9195

Booster FinalizerWatchdogDaemon修复:彻底解决TimeoutException崩溃

🚀 **FinalizerWatchdogDaemon修复** 是Booster移动应用优化工具中一个至关重要的功能,专门解决Android应用中因垃圾回收机制导致的TimeoutException崩溃问题。通过深入分析FinalizerWatchdogDaemon守护线程的工作原理,Booster提供了完整的解决方案,帮助开发者彻底告别这一常见的应用崩溃问题。 ## 什么是Finalizer

gitblog_01141的博客 1060

安卓开发中遇到的奇奇怪怪的问题(三),三面头条+四面阿里+五面腾讯拿offer分享面经总结

首先来说明一下发生问题的原因,在GC时,为了减少应用程序的停顿,会启动四个GC相关的守护线程。就是其中之一,它是用来监控线程的执行。FinalizerDaemon:析构守护线程。对于重写了成员函数finalize的对象,它们被GC决定回收时,并没有马上被回收,而是被放入到一个队列中,等待FinalizerDaemon守护线程去调用它们的成员函数finalize,然后再被回收。一旦检测到执行成员函数finalize时超出一定的时间,那么就会退出VM。我们可以理解为GC超时了。

2401_83621784的博客 789

java dofinalize_应用与系统稳定性第六篇---JVM垃圾回收之finalize执行时引起timed out 闪退分析...

一、背景java.util.concurrent.TimeoutException: android.content.res.AssetManager$AssetInputStream.finalize() timed out after 10 secondsat android.content.res.AssetManager$AssetInputStream.close(AssetManage...

weixin_33535402的博客 805

性能优化 记一次线上OOM问题处理_finalizerwatchdogdaemon

Java 中并没有析构函数, finalize() 实现了类似析构函数的概念,可以在对象被回收前做一些回收性的操作,在 JNI 开发中可能被用来进行 Native 内存的释放。FinalizerDaemon 守护线程的优先级不高,在 CPU 紧张的情况下调度可能会被影响,所以可能无法及时回收 f 类对象。finalize 对象由于 finalize() 的引用,它变成了一个强引用,即使没有其他显式的强引用了,它也还是无法立即被回收。

2401_84181309的博客 1060

Android进程整理

一、概括 系统启动架构图: 上图在Android系统-开篇中有讲解,是从Android系统启动的角度来分析,本文是从进程/线程的视角来分析该问题。 1.1 父进程 在所有进程中,以父进程的姿态存在的进程(即图中的浅红色项),如下: kthreadd进程: 是所有内核进程的父进程 init进程 : 是所有用户进程的父进程(或者父父进程) zygote进程 : 是所有上层Java...

weixin_33989058的博客 276
上一篇: Android内存优化(一)之Android常见的Java层内存泄露场景及合理的修复方案
下一篇: Android内存优化(一)之AsyncTask内存泄露使用WeakReference修复不生效
longlong2015
博客等级 码龄12年 70粉丝 39原创
评论 12
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值