Java服务一次线上问题分析解决过程

问题现象

单实例Java服务(服务使用nginx反向代理),服务假死接口返回502超时;

问题定位步骤

1、出现问题后,登录服务器本机访问了下接口,发现无响应。(nginx有配置连接超时时间,导致外部接口调用返回nginx502错误)

2、通过ps -aux | grep xxx命令找到服务对应的进程,说明服务没有挂,但是出现假死情况(接口调用没响应)。发现内存使用已达到Xmx最大值。

3、查看服务日志,发现同一现象,大概在00:00-01:00服务有java.lang.OutOfMemoryError: Java heap space。

4、确定为OOM导致springboot假死后,通过jmap --heap pid查询进程堆栈使用情况也发现堆内存占满。

5、使用jmap -dump:live,format=b,file=heap.hprof pid,导出堆中存活对象的快照信息和当前线程栈运行快照信息。

6、使用MAT工具分析,发现

776 instances of org.apache.logging.log4j.core.util.Log4jThread, loaded by org.springframework.boot.loader.LaunchedURLClassLoader @ 0x706013360 occupy 2,169,114,712 (93.95%) bytes. 有776个Log4j线程一共占用了2G内存。

7、继续分析发现每个线程里面持有了将近3w+的这个对象实例

Java服务一次线上问题分析解决过程_第1张图片

 8、结合工程log4j2的配置和log4j-core的源码分析和出问题的时间点,最终发现是每日0点,log4j日志滚动切换,当前工程使用了log4j的多线程日志打印功能且配置了日志滚动删除策略。由于这几天,突增了好几百个设备申请,每个设备都需要打印日志,导致系统有700+的设备线程组,每个设备都维护了个log4jThread,每到0点,log4jThread进行日志滚动删除,分析log4j滚动删除历史日志的源码,发现是先遍历配置的目录下所有的文件并获取文件信息。由于给设备配置的滚动删除策略中,basePath和maxDepth配置的太浅,导致这个目录下2层的文件个数有3w,每个文件持有个PathWithAttributes对象。每个对象占用了320个字节。这样320*30000*776=7G,一下子就把内存打满了。

9、内存打满后,JVM GC线程疯狂进行垃圾回收,导致GC线程把CPU打满。

问题解决

修改log4j多线程日志打印的basePath和maxDepth配置项。

你可能感兴趣的:(java,oom,log4j2)