启用JFR后批量OOM问题咨询:成因、时长关联与开销估算
问题分析与解答
背景信息
生产环境启用JFR配置如下:
-XX:StartFlightRecording=disk=true,dumponexit=true,name=profile_online,filename=/tmp/profile_`hostname`-`date +%Y_%m_%d_%H_%M_%S`.jfr,maxsize=4096m,maxage=2d,settings=/opt/app/WEB-INF/tars/prod/custom.jfc,path-to-gc-roots=false -XX:FlightRecorderOptions=maxchunksize=32m
JDK版本11.0.14,垃圾收集器为G1。启用4天后7台机器批量OOM,堆内存监控正常,但RSS从部署前15GB(峰值16GB)升至15.5GB,NMT显示Tracing指标内存开销约400MB,怀疑包含JFR内存开销。
1. 为什么OOM恰好发生在部署四天后?
你的配置中maxage=2d控制的是磁盘上保留的记录时长,但JFR运行时的内存缓存、元数据累积不受该参数直接限制。JDK11.0.14属于早期版本,存在部分JFR相关的内存泄漏或未及时回收的问题,随着运行时间增加,内存中的事件缓存、元数据会持续累积。
同时,maxsize=4096m仅限制磁盘文件总大小,不约束内存中的临时数据。当JFR的内存开销与G1垃圾收集器的本地内存占用(如大对象处理、回收阶段的额外内存)叠加后,在第4天刚好触碰到进程的内存上限(容器限制或系统ulimit),触发OOM。
2. JFR内存消耗是否与运行时长相关?
是,但正常情况下应在稳定区间波动,不会持续增长。在你的场景中出现持续增长,大概率是以下原因:
- 事件生成速度超过刷盘速度,导致内存缓冲区持续占用内存;
- JDK11.0.14的已知内存泄漏bug,部分JFR元数据、事件索引未被及时清理;
- 自定义
custom.jfc开启了高频事件(如细粒度方法采样、高频IO追踪),导致内存中事件数据累积过快。
3. JFR内存开销的主要来源?
JFR的本地内存开销核心来自四个部分:
- 事件缓冲区:分为线程本地缓冲区和全局缓冲区,暂存待写入磁盘的事件,未指定
bufferSize时,默认每个线程缓冲区256KB,全局缓冲区1MB左右,会根据事件生成量动态调整; - 元数据存储:记录类、方法、线程、字符串等事件关联元数据,避免重复写入磁盘,这部分随应用类加载、线程创建次数增长;
- 磁盘写入缓存:配合
disk=true使用,为提升写盘效率,内存中会保留待写入的32MB数据块(对应maxchunksize=32m),开销约32-64MB; - 追踪数据结构:如采样数据、事件索引,你已通过
path-to-gc-roots=false关闭了GC根路径追踪的开销。
4. 能否基于启动参数估算其开销?
可以做大致估算,但精准度受应用负载影响:
- 缓冲区内存:无自定义配置时,总开销约为
活跃线程数*256KB + 1MB,事件生成频繁时会自动扩容; - 元数据内存:取决于应用类数量、线程数量,通常在几十MB到几百MB区间,类加载频繁时会显著增长;
- 磁盘缓存:固定和
maxchunksize相关,约32-64MB; - 注意:
maxage=2d和maxsize=4096m仅控制磁盘占用,对内存开销影响极小。
临时解决方案建议
- 升级JDK到11.0.15或更高版本,修复已知的JFR内存泄漏问题;
- 检查自定义
custom.jfc配置,关闭不必要的高频事件,降低事件生成量; - 添加
-XX:FlightRecorderOptions=old-object-queue-size=10000,避免对象追踪队列溢出导致内存增长; - 定期重启应用,中断JFR内存累积过程;
- 监控NMT的
Tracing指标,设置告警阈值,提前发现内存增长趋势。
内容的提问来源于stack exchange,提问作者WtMonster
相关产品推荐
相关产品推荐

