发布时间:2022-08-19 14:12
OOM,Out Of Memory,内存溢出,发生内存溢出是会导致JVM挂掉的。
在我们启动JVM的时候,指定了一些内存参数,JVM内存大小是有限的,如果不停地往里面放东西,那么在一定情况下就会发生OOM。
那我们具体来看一看哪些区域会发生OOM,以及分别在什么条件下会发生OOM。
JVM内存区域可以划分为:方法区(元数据区),堆内存,虚拟机栈,本地方法栈,程序计数器。
其中程序计数器,是每个线程私有,而且就算保存一个字节码执行的地址,不存在内存溢出的情况。
虚拟机栈也是线程私有,里面存放的是一个一个栈帧,对应一个又一个方法的执行,出栈之后自动消失,也不存在内存溢出的情况,但是当无限入栈的时候,会导致栈溢出:Stack Over Flow。一般每个线程的栈大小为500KB,或1MB,一个栈帧大小为1KB,那么除非一个线程在执行过程中调用了500个方法,或是1000个方法,但正常代码的调用链绝不可能这么深,更通常的导致Stack Over Flow的原因在于方法自调用:
public void test(){
System.out.println(“test here”);
test();
}
像这样,就会导致执行到test()方法的线程无限入栈,最终导致Stack Over Flow。
元数据区里面存放了JVM运行过程中通过类加载器加载进去的所有类。当元数据区满了的时候会触发Full GC,Full GC的时候会回收元数据区里面的类,但是要回收一个类,条件失非常苛刻的:这个类的类加载器要先被回收,这个类的所有实例对象都要先被回收等等。如果Full GC之后元数据区还是满的,此时继续往里面放新的东西,则会触发OOM。
元数据区发生OOM的原因在于两个:
第一种原因:很多工程师不懂JVM的运行原理,没有设置元数据区大小,在上线系统的时候直接使用了默认的元数据区大小,但是对于一些大型系统来说,自身有很多的类要加载,以及依赖了较多的jar包,jar包中的类也要加载,最终导致,元数据区容纳不下需要加载的类,导致OOM。
第二种原因:很多人写代码的时候,会通过cglib之类的技术动态生成一个代理类,一旦代码没有控制好,那么就可能导致生成的类太多,本来只需要生成一个类对象即可,结果每次请求都需要生成一个类,最终导致元数据区里面放了太多cglib动态生成的类,导致元数据区OOM。
堆内存存放了所有创建的对象,而堆内存划分为了新生代和老年代,新生代在 Young GC之后,即使Survivor区放不下,也还是有老年代来提供空间担保,所以新生代是不会发生OOM。但是老年代在进行了Full GC之后,如果还是没有足够的空间来存放Young GC之后的对象,则会发生堆内存的OOM。
老年代发生OOM的原因:
第一个原因:系统承载的并发量太大,导致大量请求还处于正在处理中的状态,大量对象的引用还没有失去,大量对象存活,Young GC后放入老年代,Full GC后还是没有回收多少,继续Young GC后放入老年代,老年代OOM。
第二个原因:系统有内存泄漏的问题。在java中的内存泄漏指的是:一些对象本来已经不再会被使用到了,但是这样的对象还是有引用。导致GC时无法对这些对象进行回收,从而导致OOM。