前言
OutOfMemoryError (以下缩写为oom)是java 中最常见的内存问题,也是一旦影响就非常大的问题。oom出现的原因就是内存不够用了,GC虽然在回收,然后回收的速度赶不上新对象分配了或者根本就没有对象可以被回收,就会抛出OutOfMemoryError 错误。
在这里笔者把问题发现-问题止血-问题分析-问题规避的闭环思路来带领读者了解整个解决方案。
五种常见的oom错误
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: unable to create new native thread
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: Direct buffer memory
java.lang.OutOfMemoryError: GC overhead limit exceeded
通用手段
发现
通过以下手段可以做到感知应该出现了oom.
日志监控
通过监控日志中关键字java.lang.OutOfMemoryError,就可以知道应用是否出现oom.对于文件的监控,可以使用filebeat 或者 flume 等采集.
配置-XX:+ExitOnOutOfMemoryError
当jvm启用该参数时,如果出现了oom,就会自动退出程序,咱们的健康检测自然能发现应用不存在了,从而能发出告警
使用jstat监控jvm 内存使用率
通过jstat工具可以查看jvm的内存使用情况,如果老年代使用率一直是100%,并且期间还在一直不断GC,这也是发生了oom的一种现象。
jstat -gcutil $pid 1000 使用该命令即可实现每秒打印一次jvm的内存信息。
O列代表老年代内存使用比例,YGC和FGC分别代表新生代GC次数和老年代GC次数。
止血
当应用出现oom时,意味着应用无法申请到内存来分配对象,那么咱们的业务代码就会处于混沌状态,不清楚能进行到哪一步,不清楚状态是否完备,业务的请求可能在系统无限阻塞。这种情况对于用户体验的伤害会非常大。
所以这里有一个简单的方法就是,配置-XX:+HeapDumpOnOutOfMemoryError参数,当应用发生oom后,自动关停,借助k8s的健康检测实现自动重启能力再次拉起来新的容器。注意,这里需要实现业务系统能够实现故障转移或者请求幂等。这样才能保证用户请求不会出问题。
画外音:笔者把上面自动的地方都特殊标注了,因为自动化是效率之源,没有自动化,就得手工操作忙来忙去。
解决方案
对于 unable to create new native thread 、Metaspace、Direct buffer memory和GC overhead limit exceeded,它们的原因相对简单,可以直接给出结论。
unable to create new native thread : 无法创建更多的操作系统线程,以下3种情况:
说明当前系统的线程数过多,超过了系统线程数的上限,减少当前应用创建的线程即可。
没有native 内存了
Metaspace:
说明Metaspace不够用,修改 -XX:MaxMetaspaceSize 启动参数,调大永久代空间即可。
Direct buffer memory:
Direct ByteBuffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 Directbuffer memory 错误。
GC overhead limit exceeded:
当jvm 98%的时间都在GC时,就会出现该异常。这种情况需要增大堆或者结合下面的方法继续排查。
以下是针对 Java heap space 类型的深度分析方案
分析
当应用发生oom后,最佳的分析载体就是heap dump。通过添加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/app.dump参数,可以实现当应用发生oom时,全自动导出heap dump。
有了heap dump,我们就可以借助Eclipse MAT 这款工具来分析heap dump了
规避
oom发生的原因有两种:
应用正常,但是堆设置过小,无法支持正常的对象分配
应用发生了内存泄漏,导致应该被清理的对象没有清理,无限增长
通过分析heap dump,我们基本可以区分出两种情况,第一种情况,对象的分布都正确,没有那种以下占用30%*-,甚至50%的对象。第二种情况就是某一种类型的对象,占据了大量的内存空间。
第一种情况的解决方案就是:
增大堆内存
优化应用内存使用效率
第二种情况,就需要针对性分析了,这里笔者给出常见的oom原因,大家在问题排查时,可以逐个排除,直到找到正确答案:
ThreadLocal 未清理
出现了未指定分页的大数据量查询
定时任务中list 忘记清空,每次都追加数据
监控系统中使用了不可控的字段作为label,导致label无限增长
粉丝福利
40+最新Java场景题已整理成册,需要的小伙伴点击文末小卡片即可:
近期或者明年有面试需求的小伙伴一定要拿回去在工作之外时间好好刷刷!!!
工作8小时的态度决定的是下限,空闲的8小时才决定上限。人活在世上,要吃饭,要追求物质,功利地讲,我们要做的只有一件事,超越别人就可以了。所以空闲8个小时就是你超越别人的最好的机会。别说上班一天很累,大家都累,你不更累一点,谈啥比别人强?
以上,共勉(手动抱拳)