OOM简介及模拟场景
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。
OOM模拟场景
通过上文的分析,我们知道:只有可能在方法区(元数据区)和老年代中可能发生OOM,所以我们在这里模拟一下这两个发生OOM的场景。
元数据区发生OOM:
这里我们通过cglib来不断创建动态生成类的方式来模拟元数据区发生OOM的场景。
所谓的动态生成类,就是说这些类不是通过手写出来的。我们手写出来的类都是.java,然后会编译为.class,然后会被类加载器加载到元数据区成为静态生成的类。而动态生成的类是系统在运行的过程中,通过操作、编辑字节码从而动态生成的类。
启动JVM的时候设置元数据区大小:
-XX:MetaspaceSize=10m
-XX:MaxMetaspaceSize=10m
然后写以下代码:
public class MetaspaceOOMDemo { public static void main(String[] args) { int count = 0; while (true){ System.out.println("count: " + ++count); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Car.class);//要动态生成一个Car类的子类 enhancer.setUseCache(false);//是否对动态类进行复用 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if(method.getName().equals("run")){ System.out.println("汽车启动之前的安全检查"); return proxy.invokeSuper(obj,args);//具体去调用Car类方法的地方 }else { return proxy.invokeSuper(obj,args); } } }); Car car = (Car) enhancer.create();//这里通过enhancer生成的car就是一个继承Car类的动态类生成的对象 car.run(); } } } class Car{ public void run(){ System.out.println("汽车启动,开始行使..."); } }
以上代码中因为设置了enhancer.setUseCache(false),所以会不断的创建新的动态类,这些动态类会不断地进入到元数据区中,最终导致元数据区OOM。
启动,发现跑到底246次的时候,发生:java.lang.OutOfMemoryError: Metaspace。这里可以看到是元数据区发生的OOM,而且程序直接退出了。
老年代发生OOM:
我们通过内存泄漏的方式来模拟老年代发生OOM。
设置JVM启动参数,将堆内存设置为1MB大小:
-Xms1m –Xmx1m
编写以下代码:
public class HeapOOMDemo { public static void main(String[] args) { long count = 0; List<Object> list = new ArrayList<>(); while (true){ list.add(new Object()); System.out.println("count: " + ++count); } } }
以上代码会不断地创建Object对象,而且不会被释放,最终会因为内存泄漏问题,导致堆内存OOM。
启动,跑到第14053次的时候,发生:java.lang.OutOfMemoryError: Java heap space。堆内存溢出,而且程序直接退出了。

浙公网安备 33010602011771号