做Java程序开发,难免会遇到OutOfMemory,导致的原因也是不尽相同,下面我们来捋一捋OOM出现的场景。
一,堆空间不足,这是最容易,也是最常见的OOM。了解Java内存结构的同学应该知道,Java里面创建的对象大部分都是位于堆空间的,当创建的对象太多,而堆空间不足时,很容易抛出OOM,如下:
import java.util.ArrayList;
import java.util.List;
/*VM args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
*
* -Xms:堆的最小值;-Xmx:堆的最大值
*
* */
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List list = new ArrayList();
while (true) {
list.add(new OOMObject());
}
}
}
运行结果:
Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2760)
at java.util.Arrays.copyOf(Arrays.java:2734)
at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
at java.util.ArrayList.add(ArrayList.java:351)
at test.java.VM.OOM.HeapOOM.main(HeapOOM.java:19)
二,直接分配内存溢出,Java提供了一些可以直接操作内存和线程的低层次操作(native)-sun.misc.Unsafe,其被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。
但是丝毫不建议在生产环境中使用这个Unsafe,从名字就可以看出,这个API十分不安全、不轻便、而且不稳定。
import java.lang.reflect.Field;
import sun.misc.Unsafe;
/*VM args:-Xmx10m -XX:MaxDirectMemorySize=5m
*
* */
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField(\"theUnsafe\");
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 test.java.VM.OOM.DirectMemoryOOM.main(DirectMemoryOOM.java:19)
三,方法区溢出,方法区主要存放类的信息、静态变量、Field、Method信息等,当不停地有类动态创建并加载时,方法区也能产生OOM。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/*VM args: -XX:PermSize=5m -XX:MaxPermSize=5m
*
* */
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() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
//return method.invoke(obj, args);
return proxy.invokeSuper(obj, args);
}
});
OOMObject object = (OOMObject) enhancer.create();
}
}
static class OOMObject {
}
}
运行结果:
Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space
四,常量池溢出,常量池属于方法区,存放一些常量值(如static final),还有一些文本形式出现的符号引用,如:类和接口的全限定名、字段的名称和描述符。
import java.util.ArrayList;
import java.util.List;
/*VM rags:-XX:PermSize=5m -XX:MaxPermSize=5m
*
*
* */
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
//使用List保持着常量池引用,避免Full GC回收常量池行为
List list = new ArrayList();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());//将String对象加入常量池
}
}
}
运行结果:
Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at test.java.VM.OOM.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)
Exception in thread \"Reference Handler\" java.lang.OutOfMemoryError: PermGen space
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)
五,Stack溢出,JVM方法栈为线程私有,当方法执行时会被创建,当方法执行完毕,其对应的栈帧所占用的内存也会被自动释放。
当方法栈的深度不足时,会抛出StackOverflowError,不过只要不出现无穷递归,栈的深度不会太大。
/*VM args:-Xss128k
*
* 在单线程下,无论是由于栈帧太大,还是虚拟机容量太小,
* 当内存无法分配的时候,虚拟机抛出的都是StackOverflowError
* 如果测试不限于单线程,通过不断创建线程的方式倒是可以产生内存溢出异常(详见JavaVMStackOF2)
* */
public class JavaVMStackOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;//其实,即使没有操作数,也会throw StackOverflowError
stackLeak();
}
public static void main(String[] args) throws Throwable {
JavaVMStackOF oom = new JavaVMStackOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println(\"stack length:\" + oom.stackLength);
throw e;
}
}
}
运行结果:
stack length:1007Exception in thread \"main\" java.lang.StackOverflowError
at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:13)
at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:14)
补充:
/*VM args:-Xss10m
*
* 通过不断创建新线程达到OutOfMemoryError
* 物理内存-Xmx(最大堆容量)-MaxPermSize(最大方法区容量)=虚拟机栈+本地方法栈,程序计数器消耗内存较小,可以忽略。
* -Xss10m,分配给每个线程的内存。所以-Xss越大,越容易出现OutOfMemoryError(可创建的线程越少)。
*
* 栈深度在虚拟机默认情况下,达到1000~2000完全没问题,对于正常的方法调用(包括递归),这个深度完全够用了。
* 但是,如果是建立过多线程导致 的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆容量和减少栈容量来换取更多的线程。
* 如果没有这方面的经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到!
* */
网友评论