JVM:全面理解线上服务器内存溢出(OOM)问题处理方案(一)

0. 引言

前段时间生产上遇到了OOM问题,导致服务出现了短时间的不可用,还好处理及时,否则也将酿成大祸。OOM问题也是生产中比较重要的问题,所以本期我们针对OOM问题特别讲解,结合理论与实际案例来带大家彻底攻克OOM问题处理。

1. OOM问题产生的原因

1.1 JVM内存布局/内存模型/运行时数据区域

要解决问题,我们首先要清楚问题产生的原因。

OOM(Out Of Memory),即内存溢出,其问题表示java虚拟机在运行过程中,所占用的内存超过限制的内存大小了,导致没有多余的内存继续运行

我们要弄清楚该问题,首先要先了解java程序运行时的内存布局,我们知道java程序是运行在JVM(java虚拟机)之上的。因此其运行时的内存布局也就是JVM的内存布局。

JVM的内存布局(运行时数据区域)一共分为5部分:

  • 堆:用于存放程序运行时创建的对象或数组,是我们最常操作的内存区域。
  • 栈:用于存放栈帧,每个方法都会创建自己的栈帧,栈帧中包括局部变量表、操作栈、动态链接、返回地址等信息,其中局部变量表里存放基本数据类型和堆中对象的引用
  • 程序计数器:用来存放在一条指令所处的位置的,这里是不会发生内存溢出的,因此大家了解即可
  • 本地方法栈:与栈的作用类似,只不过栈用于管理JVM方法的调用,而本地方法栈用于管理本地方法的调用,所谓本地方法就是底层的操作系统指令、C、C++方法等
  • 方法区:每个线程共享的内存区域,用于存储已经被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据

在这里插入图片描述

1.2 常见OOM原因

我们解决OOM问题,一定要先理解JVM内存模型,如上所示,本地方法栈管理的是本地方法,相对固定,引起OOM的概率较低,而方法区存储类型信息、常量、静态变量、代码缓存之类的数据,能够出问题的无非在于静态变量数据是否过大、引入加载的class是否过多,这类问题发生的概率也比较小。计数器本身就是计数的作用,更不可能发生OOM

所以在运行时能产生OOM问题的基本就是堆和栈了,其中在实际运行中最常见的就是堆内存溢出,我们这次产生的问题也是堆内存溢出导致,堆内存溢出原因主要是以下几种
(1)创建了一个超大对象,比较常见的是一个大数组,大集合
(2)对象引用没有释放,导致垃圾无法回收,产生内存泄漏,从而导致可用内存减少
(3)突然而来的高并发,导致流量飙升,资源占用迅速提升,服务器配置无法跟上实际使用
(4)重写finalize引发频繁GC,这个问题的典型案例是小米云的C++程序员重写finalize导致了线上OOM。在java里很少见

栈存储的是基础数据类型和堆对象的引用,这些数据理论上占用并不多,要达到内存溢出,那就是不断的叠加导致的这些数据暴增,《深入理解JAVA虚拟机》中给出的栈溢出的原因是线程请求的栈深度大于虚拟机允许的深度了,所谓的栈深度就是方法嵌套调用的次数,所以说的直白点就是嵌套循环调用次数太多,即考虑如下原因:
(1)是否有递归调用
(2)是否有大量循环或死循环

如何定位是堆内存溢出还是栈内存溢出?

在java中出现堆内存溢出,可以在报错异常java.lang.OutOfMemoryError后明显看到”java heap space”的提示,即堆空间;而栈溢出可以看到StackOverflowError错误,一般这类错误在我们开发测试时就能暴露出来,所以通过报错内容的文字描述即可知道溢出位置

1.3 其他导致OOM问题的原因及解决之法

除了上述说的堆栈导致的OOM问题,其实还有其他内存区域会导致OOM问题,只是相对来说更加少见,或者基本都能在开发测试中暴露出来,很少出现在线上环境,为了让大家有个全面的了解,我们也梳理出来

首先就方法区而言,上面已经讲解到,其原因就是:
(1)静态变量过大,这个原因实际上概率很小,因为创建的静态变量一般在开发时就会把控,基本不会太大。
(2)class加载多大,比如这个项目引入了超多的jar包,编译出超多的class文件,就会导致此类问题,这类问题的解决可以通过精简项目解决,如果实在都精简不了,因为class文件在jdk1.8之前是存储在永久代的,所以可以调大永久代空间,在JVM启动脚本添加如下参数:-XX:MaxPermSize,在jdk1.8之后使用元空间替换了永久代,所以可以调整如下参数:-XX:MaxMetaspaceSize
(3)class加载了多次,这种一般是启动异常,再重启下项目即可,如果重启完问题仍未解决考虑其他原因

其次就是本地方法栈出现OOM问题:
本地方法栈出现OOM问题,一般会报错Unable to create new native thread,即无法创建新本地方法线程。这个出现的原因是:
(1)本地方法线程数超过了最大线程数限制(操作系统最大线程数ulimit和内核线程数kernel.pid_max)
(2)本地方法栈内存不足
而这两个原因的解决一般也就是三个思路:
(1)增加机器配置
(2)堆栈内存占用过多,导致本地方法栈内存占用变小,将堆栈内存调小,或者排查堆栈占用是否异常,是否有偏高的情况
(3)程序中是否有线程未正常回收,导致线程数占用,这点排查可以根据下文讲解的排查思路进行。

总结

下一章,我们来看看根据实际问题来看看如何实操解决

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYCCfBEm' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片