在Java虚拟机规范中,除了程序计数器外,虚拟机内存的其他几个运行时区域都可能会发生OutOfMemoryError异常。
在IDEA中添加JVM参数如下:
一、Java堆溢出
Java堆主要是用来存储对象,系统中不断的创建对象,并且在GC Roots到对象之间有可达路径,使垃圾回收机制不会回收这些对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。代码如下:
/**
* JVM参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\test\heapdump.hprof
* -Xms:堆的最小内存
* -Xmx:堆的最大内存
* -XX:+HeapDumpOnOutOfMemoryError :出现内存溢出时Dump出当前内存堆转储快照
* -XX:HeapDumpPath= :快照的存放路径
*/
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args) {
List list = new ArrayList();
//不停的创建OOMObject
while (true){
list.add(new OOMObject());
}
}
}
运行结果:
堆内存溢出在实际应用中还是很常见的,出现堆内存溢出时,异常信息“java.lang.OutOfMemoryError”后会进一步提示 “Java heap space”。出现这个异常可以通过分析工具分析,确认时内存泄漏还是内存溢出。如果是泄露的话,继续使用工具分析具体泄露位置;不存在泄露,看看是不是可以加大堆内存容量。
二、虚拟机栈和本地方法栈溢出
HotSpot虚拟机不区分虚拟机栈和本地方法栈,所以设置本地方法栈的参数-Xoss并没有效果;栈的容量只由-Xss参数设定。关于栈,Java虚拟机规范描述了两种异常:①如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。②如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。我们这里仅测试StackOverflowError异常如下:
/**
* JVM参数:-Xss128k
* 默认栈容量深度为:19029
* 修改栈容量为128k深度为:1001
* 通过减小栈的容量,可见栈的深度也变小。
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeak();
}catch (Throwable e){
System.out.println("Stack Length :" + oom.stackLength);
throw e;
}
}
}
运行结果:(不同机器测试结果数量可能不同)
虚拟机使用默认参数,栈深度在大多数情况下,完全够用了(包括递归)。
三、方法区(元数据区)溢出
在JDK7之前方法区可以通过PermSize永久代大小和MaxPermSize最大永久代大小设置,如:-XX:PermSize=10m -XX:MaxPermSize=10m 。但是在JDK8中已经完全移除了永久代,PermSize和MaxPermSize参数也一并移除了。在移除了Perm区域之后,JDK8中使用MetaSpace来替代,这些空间都直接在堆上来进行分配。 在JDK8中,类的元数据存放在native堆中,这个空间被叫做:元数据区。JDK8中给元数据区添加了一些新的参数:
①-XX:MetaspaceSize=
②-XX:MaxMetaspaceSize=
③-XX:MinMetaspaceFreeRatio=
④ -XX:MaxMetaspaceFreeRatio=
借助CGLib使元数据区出现内存溢出测试如下:
/**
* JVM参数:-XX:MetaspaceSize=5M -XX:MaxMetaspaceSize=5M (JDK8)
* -XX:PermSize=10m -XX:MaxPermSize=10m (JDK7)
* 需要引入cglib依赖
*/
public class JavaMethodAreaOOM {
public static void main(final 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[] objects, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj,args);
}
});
enhancer.create();
}
}
static class OOMObject{}
}
运行结果:
在很多主流框架中都是用到了CGLib,如Spring、Hibernate,需要增强的类越多,就需要越大的元数据区来保证动态生成的Class可以加载入内存。
四、本机直接内存溢出
本机直接内存溢出(程序中直接或简介使用了NIO会导致),不像上面几种OutOfMemoryError会告诉我们溢出的位置,如下:
/**
* JVM参数:-Xmx20m -XX:MaxDirectMemorySize=10m
*/
public class DircetMemoryOOM {
private static final int _1MB = 1024*1024;
public static void main(String[] args) throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true){
unsafe.allocateMemory(_1MB);
}
}
}
运行结果:
源代码:https://gitee.com/itcaofanqi/CaoFanqiStudyRepository/tree/master/stujvm
参考:周志明《深入理解Java虚拟机》