深入理解JVM之OOM

做Java程序开发,难免会遇到OutOfMemory,导致的原因也是不尽相同,下面我们来捋一捋OOM出现的场景。
一,堆空间不足,这是最容易,也是最常见的OOM。了解Java内存结构的同学应该知道,Java里面创建的对象大部分都是位于堆空间的,当创建的对象太多,而堆空间不足时,很容易抛出OOM,如下:

点击(此处)折叠或打开

  1. import java.util.ArrayList;
  2. import java.util.List;

  3. /*VM args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
  4.  *
  5.  * -Xms:堆的最小值;-Xmx:堆的最大值
  6.  *
  7.  * */
  8. public class HeapOOM {

  9.     static class OOMObject {
  10.     }

  11.     public static void main(String[] args) {
  12.         List<OOMObject> list = new ArrayList<OOMObject>();
  13.         while (true) {
  14.             list.add(new OOMObject());
  15.         }
  16.     }
  17. }
运行结果:

点击(此处)折叠或打开

  1. Exception in thread \"main\" java.lang.OutOfMemoryError: Java heap space
  2.     at java.util.Arrays.copyOf(Arrays.java:2760)
  3.     at java.util.Arrays.copyOf(Arrays.java:2734)
  4.     at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
  5.     at java.util.ArrayList.add(ArrayList.java:351)
  6.     at test.java.VM.OOM.HeapOOM.main(HeapOOM.java:19)

二,直接分配内存溢出,Java提供了一些可以直接操作内存和线程的低层次操作(native)-sun.misc.Unsafe,其被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。
但是丝毫不建议在生产环境中使用这个Unsafe,从名字就可以看出,这个API十分不安全、不轻便、而且不稳定。

点击(此处)折叠或打开

  1. import java.lang.reflect.Field;

  2. import sun.misc.Unsafe;

  3. /*VM args:-Xmx10m -XX:MaxDirectMemorySize=5m
  4.  *
  5.  * */
  6. public class DirectMemoryOOM {
  7.     private static final int _1MB = 1024 * 1024;

  8.     public static void main(String[] args) throws Exception {
  9.         Field unsafeField = Unsafe.class.getDeclaredField(\"theUnsafe\");
  10.         unsafeField.setAccessible(true);
  11.         Unsafe unsafe = (Unsafe) unsafeField.get(null);

  12.         while (true) {
  13.             unsafe.allocateMemory(_1MB);
  14.         }

  15.     }

  16. }
运行结果:

点击(此处)折叠或打开

  1. Exception in thread \"main\" java.lang.OutOfMemoryError
  2.     at sun.misc.Unsafe.allocateMemory(Native Method)
  3.     at test.java.VM.OOM.DirectMemoryOOM.main(DirectMemoryOOM.java:19)

三,方法区溢出,方法区主要存放类的信息、静态变量、Field、Method信息等,当不停地有类动态创建并加载时,方法区也能产生OOM。

点击(此处)折叠或打开

  1. import java.lang.reflect.Method;

  2. import net.sf.cglib.proxy.Enhancer;
  3. import net.sf.cglib.proxy.MethodInterceptor;
  4. import net.sf.cglib.proxy.MethodProxy;

  5. /*VM args: -XX:PermSize=5m -XX:MaxPermSize=5m
  6.  *
  7.  * */
  8. public class JavaMethodAreaOOM {
  9.     public static void main(String[] args) {
  10.         while (true) {
  11.             Enhancer enhancer = new Enhancer();
  12.             enhancer.setSuperclass(OOMObject.class);
  13.             enhancer.setUseCache(false);
  14.             enhancer.setCallback(new MethodInterceptor() {
  15.                 @Override
  16.                 public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
  17.                     //return method.invoke(obj, args);
  18.                     return proxy.invokeSuper(obj, args);
  19.                 }
  20.             });
  21.             OOMObject object = (OOMObject) enhancer.create();
  22.         }

  23.     }

  24.     static class OOMObject {
  25.     }
  26. }
运行结果:

点击(此处)折叠或打开

  1. Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space

四,常量池溢出,常量池属于方法区,存放一些常量值(如static final),还有一些文本形式出现的符号引用,如:类和接口的全限定名、字段的名称和描述符。

点击(此处)折叠或打开

  1. import java.util.ArrayList;
  2. import java.util.List;

  3. /*VM rags:-XX:PermSize=5m -XX:MaxPermSize=5m
  4.  *
  5.  *
  6.  * */
  7. public class RuntimeConstantPoolOOM {
  8.     public static void main(String[] args) {
  9.         //使用List保持着常量池引用,避免Full GC回收常量池行为
  10.         List<String> list = new ArrayList<String>();

  11.         int i = 0;
  12.         while (true) {
  13.             list.add(String.valueOf(i++).intern());//将String对象加入常量池
  14.         }
  15.     }
  16. }
运行结果:

点击(此处)折叠或打开

  1. Exception in thread \"main\" java.lang.OutOfMemoryError: PermGen space
  2.     at java.lang.String.intern(Native Method)
  3.     at test.java.VM.OOM.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:17)
  4. Exception in thread \"Reference Handler\" java.lang.OutOfMemoryError: PermGen space
  5.     at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:123)

五,Stack溢出,JVM方法栈为线程私有,当方法执行时会被创建,当方法执行完毕,其对应的栈帧所占用的内存也会被自动释放。
当方法栈的深度不足时,会抛出StackOverflowError,不过只要不出现无穷递归,栈的深度不会太大。

点击(此处)折叠或打开

  1. /*VM args:-Xss128k
  2.  *
  3.  * 在单线程下,无论是由于栈帧太大,还是虚拟机容量太小,
  4.  * 当内存无法分配的时候,虚拟机抛出的都是StackOverflowError
  5.  * 如果测试不限于单线程,通过不断创建线程的方式倒是可以产生内存溢出异常(详见JavaVMStackOF2)
  6.  * */
  7. public class JavaVMStackOF {
  8.     private int stackLength = 1;

  9.     public void stackLeak() {
  10.         stackLength++;//其实,即使没有操作数,也会throw StackOverflowError
  11.         stackLeak();
  12.     }

  13.     public static void main(String[] args) throws Throwable {
  14.         JavaVMStackOF oom = new JavaVMStackOF();
  15.         try {
  16.             oom.stackLeak();
  17.         } catch (Throwable e) {
  18.             System.out.println(\"stack length:\" + oom.stackLength);
  19.             throw e;
  20.         }
  21.     }
  22. }
运行结果:

点击(此处)折叠或打开

  1. stack length:1007Exception in thread \"main\" java.lang.StackOverflowError

  2.     at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:13)
  3.     at test.java.VM.OOM.JavaVMStackOF.stackLeak(JavaVMStackOF.java:14)

补充:

点击(此处)折叠或打开

  1. /*VM args:-Xss10m
  2.  *
  3.  * 通过不断创建新线程达到OutOfMemoryError
  4.  * 物理内存-Xmx(最大堆容量)-MaxPermSize(最大方法区容量)=虚拟机栈+本地方法栈,程序计数器消耗内存较小,可以忽略。
  5.  * -Xss10m,分配给每个线程的内存。所以-Xss越大,越容易出现OutOfMemoryError(可创建的线程越少)。
  6.  *
  7.  * 栈深度在虚拟机默认情况下,达到1000~2000完全没问题,对于正常的方法调用(包括递归),这个深度完全够用了。
  8.  * 但是,如果是建立过多线程导致 的内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆容量和减少栈容量来换取更多的线程。
  9.  * 如果没有这方面的经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到!
  10.  * */

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/28912557/viewspace-1455299/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/28912557/viewspace-1455299/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值