记一次java进程异常退出原因排查

最近在对一个Java服务进行压测,但是压测一段时间后,java进程就会自动停止,虽然怀疑可能是内存不足原因导致的,但是从服务日志中去看,并没有OOM的相关报错日志。这就很奇怪了!然后就从Java启动参数入手!

 -XX:+HeapDumpOnOutOfMemoryError=/path/to/save/dump.hprof

该参数用于在 JVM 发生 OOM 时自动生成内存 dump 文件。

 我在服务启动参数中加了如上配置后,jvm停止后并没有生成hprof文件。而且还有一个发现就是,加上这个参数之后,jvm停止能抗住压测的时间变的更短了,充分猜测该参数是会产生额外的内存占用的。

那么是不是不是OOM造成的jvm退出的锅呢?

-XX:ErrorFile=./hs_err_pid<pid>.log

当jvm出现致命错误时,会生成一个错误文件 hs_err_pid.log,其中包括了导致jvm crash的重要信息,可以通过分析该文件定位到导致crash的根源,从而改善以保证系统稳定。当出现crash时,该文件默认会生成到工作目录下

 加了上如上的参数后,本来觉得可以生成对应的文件,但是java进程退出后,以来没有任何文件生成。这个时候java进程退出的原因成了谜团。

1.应用日志没有报致命错误

2.添加jvm启动参数,监控jvm退出,没有生效

3.应用程序没有System.exit()相关操作,那究竟是什么导致了java进程的退出呢?

在这里进行补充说明一下,服务器内存8G,JVM堆内存配置:Xmx6g Xms6g。

在刚开始压测的时候,使用jvisualvm来监控jvm,其内存占用没有异常比较稳定,并且压测也采用的是固定tps进行的压测。

那么究竟什么原因导致的进程退出呢?经过不断研究。发现可以查看系统日志来找到原因。

通过查阅[ redhat 系操作系统 /var/log/messages  debian 系操作系统 /var/log/syslog ]日志,可以看到是因为操作系统内存耗尽,触发了OOM killer,杀死了服务

如图所示,在日志中搜索java关键字,果然找到了OOM的证据,同时标明了进程号和时间。

这里 我们也可以看到这个系统日志的各个字段的含义。

那么原因应该也比较好解释了,那就是操作系统内存不足,导致系统内核停止了java进程,导致jvm根本无法顾及生成堆文件及错误文件。

回过头来看,其实我们的OOM发生在操作系统层,和我们平时遇到的JVM的OOM是不一样的。这里需要介绍一下OOM killer:操作系统在给应用程序分配内存时,采用的是over-commit策略,即无论应用需要多少内存,操作系统都会允许,即使超过了目前剩余的容量,操作系统依然允许。这是因为操作系统觉得大部分的应用程序并不会用满他自己声明的内存。但是还是可能会出现大家都用满了自己声明的内存的情况,这个时候操作系统如果不释放内存出来,很可能连自己都无法运行了,所以这个时候操作系统就会叫OOM killer过来,给所有的进程打分,选出得分最高者,直接kill掉。一般都是内存占用最高的进程被kill掉。

并且由于JVM不会自动归还HeapSize,就算你实际使用的heap大小只有一点点,操作系统还是认为你占用了这么多的HeapSize(这也就是为什么当java进程打满jvm设置的最大堆内存后,即使服务没有任何负载,此时查看服务器内存占用还是可以发现java进程占用的内存空间略高于跟jvm设置的最大堆内存空间,然后java进程刚启动时服务器进程内存占用是小于jvm设置的最大堆内存空间的)。所以这就解释了为什么我们需要压测一段时间才会发生这个场景,那是因为由初始的HeapSize达到MaxHeapSize需要长时间的运行以积累足够的对象触发JVM的Heap扩展策略。也解释了在每次进程消失的时间点,我们实际上没有做什么消耗资源的操作,但进程还是退出了,那是因为操作系统只认你占领的内存空间,并不会管你实际使用了多少空间。

PS:由于一个java应用是单进程程序,所以当OOM killer选择了java的时候,整个java世界就会瞬间崩塌,JVM无法做任何响应就退出了,无法做任何gracefully shutdown,也不会有任何日志文件写出。

按照JVM默认的策略,MaxHeapSize默认是四分之一的内存空间,InitialHeapSize是64分之一的内存空间大小。当然可以手动配置JVM的最大堆(-Xmx)和初始堆(-Xms)的大小,具体大小值需要根据实际情况而定,设置得太大容易被OOM killer杀掉,设置地太小容易发生十分频繁的GC操作。在测试环境中,可以尽量往小配置,避免其他应用影响。加上 -XX:+HeapDumpOnOutOfMemoryError ,便于在JVM发生OOM时,可以还原“事故现场” (需要注意的是:OOM dump 本身也需要利用堆外内存,当堆外内存达到上限时,触发系统的 oomkiller 机制把java进程杀死,这带来一个小问题:如果要保证 JVM OOM 自动 dump 机制能顺利执行,我们就需要在操作系统里预留出足够的堆外内存,这就带来内存利用率的问题了。如果 JVM 直接运行在宿主操作系统,没有容器的限制,能申请的堆外内存是受限于系统能分配的内存的,不同应用的 JVM 可共享这个可分配内存空间。)。

当然这次导致jvm进程退出的原因是操作系统内核导致的,那有没有可能是代码问题呢?其中一个重要的点就是内存泄漏。

内存泄露和内存溢出是两个不同的概念。内存泄露关心的是无用对象,内存泄漏最终会导致内存溢出。除了内存泄露,在瞬时读取大量数据至内存中也是会导致内存溢出的,比如数据库返回大量的对象,或者读取了一张很大的图片在内存中。

经典的、常见的内存泄漏的原因有以下几个方面:

静态的集合类:由于这些变量的生命周期与应用程序保持一致,所以会导致这些类中的对象就算被手动置为null,也无法被释放。

单例:同1,单例类的生命周期与应用程序也保持一致,如果单例类中也保持了某些对象,那么这些对象也是无法被回收的。

各种连接、IO:这些操作都是十分消耗资源的,如果忘记了关闭,比如忘记关闭数据库连接,那么也是会发生内存泄漏的情况。

threadlocal:由于threadlocal变量的生命周期与线程保持一致,当与线程池结合使用时,由于线程使用完毕后不会销毁,所以threadlocal变量可能也会出现泄漏的情况。

其他:比如监听器,外部模块调用等。。

大体上所有的内存泄露的原因都可以通过研究Heap Dump文件(堆转储)来解决。JVM可以通过jmap命令dump出当前的heap快照,通常后缀名为.hprof。JVM支持在OOM时,dump出当前的heap,便于开发人员分析为何出现了OOM。

参考文档:某Java服务异常退出原因排查过程分享

https://www.jianshu.com/p/58aa61cb22fb

https://blog.csdn.net/weixin_39247141/article/details/126304526

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值