JVM内存溢出的测试

    本人初学java,看到面试官问道JVM的问题蛮多,就学了一点点,拿出来和大家分享,有什么错误的地方,希望大家指出来。
    要解决内存溢出的问题,我们说为什么内存溢出。运行一个大型的软件或游戏,所需要的内存空间远远超过了,你主机内安装的内存所承受的大小,就叫内存溢出。
    对于虚拟机而言,JVM包括一组寄存器,一个栈,一个垃圾回收堆一个存储方法域。我们的实例对象会放在java堆空间,普通对象存放在栈中,以及方法区,常量池等等,还有本机内存,所以对于内存溢出的问题,我们已经有眉目从哪些地方找到元凶了。
A:java堆溢出
java堆用于存放我们的实例对象,当我们不断的创建对象,并一直持有对象的引用,防止被gc。
测试如下:
[img]package demo;

import java.util.ArrayList;
import java.util.List;

/**
* VM Args:-verbose:gc -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
* */
public class HeapOverFlow {
public static void main(String[] args) {
List<HeapOverFlow> hofs=new ArrayList<HeapOverFlow>();
while(true){
hofs.add(new HeapOverFlow());
}
}
}
[/img]

设置xms为20m,xmx为20m,执行效果如下:

[img]
[GC 17190K->17190K(20352K), 0.0071101 secs]
[GC 18470K->18469K(20352K), 0.0078375 secs]
[GC 18573K->18558K(20352K), 0.0011519 secs]
[Full GC 18558K->18558K(20352K), 0.0546033 secs]
[Full GC 18558K->17808K(20352K), 0.0763401 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid4784.hprof ...
Heap dump file created [32577359 bytes in 0.842 secs]
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 demo.HeapOverFlow.main(HeapOverFlow.java:13)

[/img]
发生这种情况具体还要看是内存泄露还是内存溢出。如果存在内存泄露就要找到泄露点,如果是内存溢出,则需要增加内存空间。至于怎么做,目前还没研究哈。

B:虚拟机栈和本地方法栈溢出

由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法区栈,因此对于HotSpot来说,-Xoss(设置本地方法栈大小)参数是无效的,栈容量由-Xss参数设定。关于虚拟机栈和本地方法区栈,在Java虚拟机规范中描述了两种异常:
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常

《深入浅出java虚拟机》这本书中谈到单线程的场景下只能浮现StackOverflowError,那我们就先来看看单线程场景下到底会是什么样子。

[img]public class StackOverFlow {
private int stackLength = 1;
  private void stackLeak() {
    stackLength++;
    stackLeak();
  }
 
  public static void main(String[] args) throws Throwable {
  StackOverFlow oom = new StackOverFlow();
    try {
      oom.stackLeak();
    } catch (Throwable e) {
      System.out.println("stack length:" + oom.stackLength);
      throw e;
    }
  }
  }[/img]
设置-Xss128k 运行结果如下:

[img]stack length:2402
Exception in thread "main" java.lang.StackOverflowError
at demo.StackOverFlow.stackLeak(StackOverFlow.java:11)
at demo.StackOverFlow.stackLeak(StackOverFlow.java:12)[/img]

通过不断创建线程耗尽内存也可以呈现出OutOfMemoryError异常,由于在windows下会造成死机,这里我就不试了。

C:运行时常量池溢出

向运行时常量池中添加内容最简单的方式就是使用String.intern()方法。由于常量池分配在方法区内,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

例子:
import java.util.ArrayList;
import java.util.List;

/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
* @author Administrator
*
*/
public class StaticConstantPoolOF {
public static void main(String[] args) {

      List<String> list = new ArrayList<String>();

      int i = 0;

      while (true) {
        list.add(String.valueOf(i++).intern());
      }
    }
}

运行结果如下:
[img][GC 3159K->2263K(5056K), 0.0005486 secs]
[Full GC 2403K->1018K(5056K), 0.0387303 secs]
[Full GC 1018K->1018K(5056K), 0.0416562 secs]
[Full GC 1033K->242K(5056K), 0.0150953 secs]
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at demo.StaticConstantPoolOF.main(StaticConstantPoolOF.java:24)[/img]

D:方法区溢出

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。测试这个区域只要在运行时产生大量的类填满方法区,知道溢出。《深入理解Java虚拟机》借助CGlib直接操作字节码运行时,生成了大量的动态类。

当前主流的Spring和Hibernate对类进行增强时,都会使用到CGLib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载到内存。
[img]import net.sf.cglib.proxy.Enhancer;
  import net.sf.cglib.proxy.MethodInterceptor;
  import net.sf.cglib.proxy.MethodProxy;

  /**
   * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
   *
   */
  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 object, Method method,
              Object[] args, MethodProxy proxy) throws Throwable{
            return proxy.invokeSuper(object, args);
          }
        });
        enhancer.create();
      }

    }

    static class OOMObject {

    }

  }[/img]

工程中要引入cglib-2.2.2.jar和asm-all-3.3.jar。
方法区的内存溢出问题同样存在jdk6和jdk7版本之间的区别,同运行时常量池内存溢出。
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收掉,判定条件是非常苛刻的。在经常动态生成大量Class的应用中,需要特别注意类的回收状况。这类场景除了上面提到的程序使用了CGLib字节码增强外,常见的还有:大量JSP或动态生成JSP文件的应用、基于OSGi的应用等。

E:本机直接内存溢出

DirectMemory容量可以通过-XX:MaxDirectMemorySize指定。

  import java.lang.reflect.Field;
  import sun.misc.Unsafe;

  /**
   * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
   *
   */
  public class DirectMemoryOOM {

    private static final int _1MB = 1024 * 1024;

    /**
     * @param args
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    public static void main(String[] args) throws IllegalArgumentException,
        IllegalAccessException {
      // TODO Auto-generated method stub
      Field unsafeField = Unsafe.class.getDeclaredFields()[0];
      unsafeField.setAccessible(true);
      Unsafe unsafe = (Unsafe) unsafeField.get(null);
     
      while(true){
        unsafe.allocateMemory(_1MB);
      }
    }

  }
该例子还没测试。

猜你喜欢

转载自xiaofeiyan.iteye.com/blog/1936057