[转帖]JVM 输出 GC 日志导致 JVM 卡住,我 TM 人傻了
https://www.jianshu.com/p/51380e04eab1
最近,我们升级了 Java 17。后来,我们的 k8s 运维团队为了优化我们的应用日志采集, 将我们所有 pod (你可以理解为一个 Java 微服务进程)的 JVM 日志都统一采集到同一个 AWS 的 EFS 服务(EFS 是 Elastic File System 的缩写,弹性块文件存储系统,底层是 NFS + S3 对象存储集群) ,我们对于 JVM 日志配置包括以下几个:
- GC日志:-Xlog:gc*=debug:file=${LOG_PATH}/gc%t.log:utctime,level,tags:filecount=50,filesize=100M
- JIT 编译日志:-Xlog:jit+compilation=info:file=${LOG_PATH}/jit_compile%t.log:utctime,level,tags:filecount=10,filesize=10M
- Safepoint 日志:-Xlog:safepoint=trace:file=${LOG_PATH}/safepoint%t.log:utctime,level,tags:filecount=10,filesize=10M
- 关闭堆栈省略:这个只会省略 JDK 内部的异常,比如 NullPointerException 这种的:-XX:-OmitStackTraceInFastThrow,我们应用已经对于大量报错的时候输出大量堆栈导致性能压力的优化,
在这样做之后,我们的应用出现这样一个奇怪的问题,这个问题有三种不同的现象,统一的表现是 处于安全点的时间特别特别长 :
1.通过 safepoint 日志看出来,等待所有线程进入安全点的时间特别长(Reaching safepoint:25s多)

2.通过 safepoint 日志看出来,还有处于 safepoint 时间过长的,并且原因是 GC(At safepoint: 37s多)

查看 GC 日志, Heap before GC invocations
与输出堆结构的日志间隔了很久:

3.另一种处于 safepoint 时间过长的,原因也是 GC,但是间隔日志的地方不一样(29s多)
查看 GC 日志,输出堆结构的日志某些间隔了很久:

问题定位
首先,Java 应用线程整体处于 safepoint,这时候应用线程什么都做不了, 所以依赖应用线程的监控即通过 JVM 外部监控,例如 spring actuator 暴露的 prometheus 接口,以及 Skywalking 插桩监控,是什么都看不到的 ,只会看到出于安全点时调用的这些方法时间特别长,但是并不是这些方法真的有瓶。
需要通过 JVM 内部线程的监控机制,例如 JVM 日志,以及 JFR(Java Flight Recording)来定位 。还有就是通过 async_profiler ( https://github.com/jvm-profiling-tools/async-profiler/ ),因为我们发现,在出问题的时候,进程本身的 CPU 占用(注意不是机器的,是这个进程的)也会激增:

但是非常奇怪的是,通过 async_profiler 查看 CPU 占用,发现出问题的时间段,除了:
并且 在处于安全点的期间,日志也是被中断了一样,这是非常少见的 ,为什么这么说,请看下面分析:
针对现象一,等待所有线程进入 safepoint 时间特别长,这个一般会不断输出等待哪个线程没有进入安全点的日志,参考 JVM 源码:
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/safepoint.cpp

但是现象一中我们并没有看到因为哪个线程导致进入 safepoint 时间过长。
针对现象二,通过 JFR,也没看出 GC 的哪个阶段耗时很长:

针对现象三,通过查看 JVM 源码发现,输出这两个间隔很大的日志的代码之间,没有做任何的事情,只是打日志。并且查看所有出异常的时间点, 都是每个小时的 05 分左右 , 询问运维知道在这个时间,会进行上一小时日志文件的移出与与 EFS 同步 (我们一个小时生成一个日志文件), 会有大量文件 IO (由于底层使用的是云服务,也许并不是磁盘,而是 EFS 这种 NFS 或者网络对象存储)。会不会是文件 IO 太大导致 JVM 日志输出堵住导致 JVM 卡住呢?
为啥 JVM 日志输出会导致 JVM 所有应用线程卡住,假设 JVM 某个线程输出日志卡住了,倘若没有处于 safepoint,那么不会卡住所有应用线程,只会卡住它自己。但是如果处于 safepoint,所有应用线程本身就被暂停了,如果这个时候某个 JVM 线程输出日志卡住,那么可能造成迟迟不能所有线程进入安全点,或者所有处于安全点时间过长。对应现象一,某个线程输出的是 JVM 日志而不是应用日志(输出应用日志一般是涉及文件 IO 原生调用,处于原生调用直接就算进入了安全点,不会有影响, 输出 JVM 日志卡住导致这个线程迟迟没有进入安全点。针对现象二三,都是 GC 线程输出 JVM 日志卡住导致 GC 迟迟不结束。
首先通过 JVM 源码确认下 JVM 日志输出卡住是否会阻塞 JVM。
JVM 输出 JVM 日志源码分析
我们使用的是 Java 17,Java 17 之前没有异步 JVM 日志输出。所以待会的源码分析请忽略异步日志的代码,这样就是 Java 17 前的日志输出:
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/logging/logFileStreamOutput.cpp
[图片上传失败...(image-99946c-1655454045620)]
通过这里的代码可以看出,如果输出文件 IO 卡住,这里的 flush 是会卡住的。同时,会有短暂的 CPU 激增,因为刷入等待的策略应该是 CPU 空转等待一段时间之后进入阻塞。
那么我们换成异步日志怎么样?异步日志有哪些参数呢? JVM 异步日志是 Java 17 引入的 ,对应的 ISSUE 是: https://bugs.openjdk.org/browse/JDK-8229517,其中的关键,在于这两个参数:

通过 -Xlog:async 启用 JVM 异步日志,通过 -XX:AsyncLogBufferSize= 指定异步日志缓冲大小,这个大小默认是 2097152 即 2MB。异步日志的原理是:

修改参数为异步日志,问题大幅度缓解,但是并没完全解除,进一步定位
我们修改日志为异步日志,加入启动参数: -Xlog:async,-XX:AsyncLogBufferSize=4194304。之后观察,问题得到大幅度缓解:

但是还是在某一个实例上出现了一次问题, 查看现象,与之前的不同了,通过 safepoint 日志看,是某个线程一直 running 不愿意不进入 safepoint :

那么这个线程在干什么呢?通过 jstack 看一下这个线程是什么线程:

这是一个定时刷新微服务实例列表的线程,代码对于 WebFlux 的使用并不标准:

这样使用异步代码,可能带来 JIT 优化错误(正确的用法调用很频繁,这个错误用法调用也很频繁,导致 JIT C2 不断优化与去优化),查看 JFR 发现这段时间也有很多 JIT 去优化:

这样可能导致安全点缺失走到 IO 不断空转等待很久的问题,需要改成正确的用法:

修改好之后,迟迟不进入 safepoint 的问题消失。
[转帖]JVM 输出 GC 日志导致 JVM 卡住,我 TM 人傻了的更多相关文章
- JVM 输出 GC 日志导致 JVM 卡住,我 TM 人傻了
本系列是 我TM人傻了 系列第七期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了:https://zhuanlan.zhihu.com/p/3970425 ...
- 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了
最近我们项目升级到了 Spring Boot 2.4.6 + Spring Cloud 2020.0.x,通过我的另一系列即可看出:Spring Cloud 升级之路.但是升级后,我们发现 Young ...
- 获取异常信息里再出异常就找不到日志了,我TM人傻了
本系列是 我TM人傻了 系列第三期[捂脸],往期精彩回顾: 升级到Spring 5.3.x之后,GC次数急剧增加,我TM人傻了 这个大表走索引字段查询的 SQL 怎么就成全扫描了,我TM人傻了 最近组 ...
- jvm的GC日志分析 [转]
jvm的GC日志分析 标签: jvm内存javagc 2015-06-22 16:37 1566人阅读 评论(1) 收藏 举报 分类: Java(4) JVM的GC日志的主要参数包括如下几个: ...
- JVM学习之Eclipse输出GC日志
Java应用启动时,可以通过设置verbose参数来输出JVM的gc情况,命令如下:-verbose:gc或者-XX:+PrintGC在Eclipse中可以通过Run As|Run Configura ...
- 曹工杂谈:手把手带你读懂 JVM 的 gc 日志
一.前言 今天下午本来在划水,突然看到微信联系人那一个红点点,看了下,应该是博客园的朋友.加了后,这位朋友问了我一个问题: 问我,这两块有什么关系? 看到这段 gc 日志,一瞬间脑子还有点懵,嗯,这个 ...
- jvm之gc日志
ava GC日志可以通过 +PrintGCDetails开启 以ParallelGC为例 YoungGC日志解释如下 FullGC:
- JVM内存GC的骗局——JVM不抛出OOM但内存已经泄露
概述 在日常测试中,我们会去重点观察java的内存使用情况,比如:进程会抛出OOM异常,不再接收新的请求:响应时间在固定时间段内变长,超时或者不响应,CPU使用率时常像过山车一样等.有时候JVM还会发 ...
- JVM OOM异常会导致JVM退出吗?
出处: https://mp.weixin.qq.com/s/8j8YTcr2qhVActLGzOqe7Q https://blog.csdn.net/h2604396739/article/de ...
- 日志导致jvm内存溢出相关问题
生产环境日志级别为info,请看如下这行代码: LOGGER.debug("the DTO info: {}", JSON.toJSONString(DTO)); 这段代码主要有两 ...
随机推荐
- MySQL运维实战(1.1)安装部署:使用RPM进行安装部署
作者:俊达 我们在生产环境部署mysql时,一般很少使用RedHat Package Manager(RedHat软件包管理工具).用rpm或或者其他包管理器安装mysql有其好处,例如安装简单,并且 ...
- Python汉诺塔递归算法实现
关于用递归实现的原理,请查看我之前的文章: C语言与汉诺塔 C#与汉诺塔 以下为代码: count = 0 def move(pile, src, tmp, dst): global count if ...
- Spring Boot与Shiro和Mybatis整合
1:shiro是什么? Apache Shiro 是ASF旗下的一款开源软件 shrio是一款强大而灵活的安全框架 可为任何应用提供安全保障- 从命令行应用.移动应用到大型网络及企业应用 2:shir ...
- 这些常见的python编码习惯,你都会吗
本文分享自华为云社区<不得不知的十个常见PY编码习惯>,作者:码乐. 简介 语言在发展和变化,编码习惯也在发生改变.这里简单聊聊 17个python中常见的编码习惯或者风格. 1,可变数据 ...
- Java异常中处理return
Java中的return语句 在Java中,return语句用于从一个方法中返回结果,并终止当前方法的执行.在方法中使用return语句后,后续的语句将不会执行. public int add(int ...
- 储存入redis并且将次数进行+1
// 储存入redis并且将次数进行+1stringRedisTemplate.opsForValue().increment(RedisKey.LOGIN_FATL_NUM + "1348 ...
- CUDA C编程权威指南:1.3-CUDA基础知识点梳理
主要整理了N多年前(2013年)学习CUDA的时候开始总结的知识点,好长时间不写CUDA代码了,现在LLM推理需要重新学习CUDA编程,看来出来混迟早要还的. 1.CUDA数组 解析:CUDA数组 ...
- 神经网络入门篇:直观理解反向传播(Backpropagation intuition)
详细推导反向传播 下图是逻辑回归的推导: 回想一下逻辑回归的公式(参考公式1.2.公式1.5.公式1.6.公式1.15) 公式1.38: \[\left. \begin{array}{l} {x }\ ...
- 干货分享丨轻松玩转 Huawei LiteOS 传感框架
摘要:LiteOS传感框架将物联网终端设备上不同类型的传感器统一管理,通过抽象不同类型传感器接口,屏蔽其硬件细节,做到"硬件"无关性,非常方便于物联网设备的开发.维护和功能扩展. ...
- 当AI抄起了水表
摘要:一套AI工作流,既减轻水表抄表工的负担,也保证了普通百姓用水数据的真实. 本文分享自华为云社区<行业案例:当AI抄起水表,一套工作流打通水务智能的"任督二脉">, ...