1.JAVA堆导致的内存异常
Java堆存放的是对象实例,因此只要不断建立对象,并且保证GC Roots到对象之间有可达路径即可产生OOM异常。这是由于内存泄露导致的内存溢出异常。代码如下:
package MemoryTest; import java.util.ArrayList; import java.util.List; /** * Java堆OOM测试 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * @author zzm */ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while (true) { list.add(new OOMObject()); } } }
测试中限制Java堆大小为20M,不可扩展,通过参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现OOM异常的时候Dump出内存映像以便分析。
注:虚拟机参数设置。窗口->首选项->JAVA->已安装的JRE,在缺省的VM自变量中增加。
运行结果:
stack length:11347 Exception in thread "main" java.lang.StackOverflowError at MemoryTest.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:16)
关于VM栈和本地方法栈在VM Spec描述了两种异常:StackOverflowError与OutOfMemoryError,当栈空间无法继续分配分配时,到底是内存太小还是栈太大其实某种意义上是对同一件事情的两种描述而已.
1.使用-Xss参数削减栈内存容量。结果:抛出SOF异常时的堆栈深度相应缩小。
2.定义大量的本地变量,增大此方法对应帧的长度。结果:抛出SOF异常时的堆栈深度相应缩小。
3.创建几个定义很多本地变量的复杂对象,打开逃逸分析和标量替换选项,使得JIT编译器允许对象拆分后在栈中分配。结果:实际效果同第二点。
stack length:2402 Exception in thread "main" java.lang.StackOverflowError at MemoryTest.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14) at MemoryTest.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:16)
如果在多线程环境下,不断建立线程倒是可以产生OOM异常,但是基本上这个异常和VM栈空间够不够关系没有直接关系,甚至是给每个线程的VM栈分配的内存越多反而越容易产生这个OOM异常。
原因其实很好理解,操作系统分配给每个进程的内存是有限制的,譬如32位Windows限制为2G,Java堆和方法区的大小JVM有参数可以限制最大值,那剩余的内存为2G(操作系统限制)-Xmx(最大堆)-MaxPermSize(最大方法区),程序计数器消耗内存很小,可以忽略掉,那虚拟机进程本身耗费的内存不计算的话,剩下的内存就供每一个线程的VM栈和本地方法栈瓜分了,那自然每个线程中VM栈分配内存越多,就越容易把剩下的内存耗尽。
创建线程导致OOM异常
package MemoryTest; /* * 创建线程导致OOM异常 * VM Args:-Xss2M (这时候不妨设大些) * @author zzm */ public class JavaVMStackOOM { private void dontStop() { while (true) { } } public void stackLeakByThread() { while (true) { Thread thread = new Thread(new Runnable() { @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) throws Throwable { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }
设置好虚拟机参数后,运行上面的代码。特别提示运行上面这段代码,记得要存盘当前工作,上述代码执行时有很大令操作系统卡死的风险。结果:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
运行时常量池
要在常量池里添加内容,最简单的就是使用String.intern()这个Native方法。由于常量池分配在方法区内,我们只需要通过-XX:PermSize和-XX:MaxPermSize限制方法区大小即可限制常量池容量。实现代码如下:
package MemoryTest; import java.util.ArrayList; import java.util.List; /* * 运行时常量池导致的OOM异常 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持着常量池引用,压制Full GC回收常量池行为 List<String> list = new ArrayList<String>(); // 10M的PermSize在integer范围内足够产生OOM了 int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } } }
设置好虚拟机参数后运行,结果如下:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at MemoryTest.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:29)
方法区
方法区用于存放Class相关信息,所以这个区域的测试我们借助CGLib直接操作字节码动态生成大量的Class,值得注意的是,这里我们这个例子中模拟的场景其实经常会在实际应用中出现:当前很多主流框架,如Spring、Hibernate对类进行增强时,都会使用到CGLib这类字节码技术,当增强的类越多,就需要越大的方法区用于保证动态生成的Class可以加载入内存。
package MemoryTest; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodInterceptor; /* 需要下载cglib * 借助CGLib使得方法区出现OOM异常 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm */ public class JavaMethodAreaOOM { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor(){ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } static class OOMObject { } }
结果:
Caused by: java.lang.OutOfMemoryError: PermGen space at java.lang.ClassLoader.defineClass1(Native Method)
本机直接内存
package MemoryTest; import java.lang.reflect.Field; import sun.misc.Unsafe; /* * 本机直接内存 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M * @author zzm */ public class DirectMemoryOOM { private static final int _1MB = 1024 * 1024; public static void main(String[] args) throws Exception { Field unsafeField = Unsafe.class.getDeclaredFields()[0]; unsafeField.setAccessible(true); Unsafe unsafe = (Unsafe) unsafeField.get(null); while (true) { unsafe.allocateMemory(_1MB); } } }
设置好参数后运行,结果
Exception in thread "main" java.lang.OutOfMemoryError at sun.misc.Unsafe.allocateMemory(Native Method) at MemoryTest.DirectMemoryOOM.main(DirectMemoryOOM.java:35)
2.VM栈和本地方法栈
设置虚拟机参数后运行程序,结果如下: