实战OutOfMemoryError

在VM运行时数据区域中,除了程序计数器,其余在VM Spec中都描述了产生OutOfMemoryError(下称OOM)的情形,那咱们就实战模拟一下,经过几段简单的代码,令对应的区域产生OOM异常以便加深认识,同时初步介绍一些与内存相关的虚拟机参数。下文的代码都是基于Sun Hotspot虚拟机1.6版的实现,对于不一样公司的不一样版本的虚拟机,参数与程序运行结果可能结果会有所差异。 java

1.Java堆 多线程

Java堆存放的是对象实例,所以只要不断创建对象,而且保证GC Roots到对象之间有可达路径便可产生OOM异常。测试中限制Java堆大小为20M,不可扩展,经过参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现OOM异常的时候Dump出内存映像以便分析。(关于Dump映像文件分析方面的内容,可参见《JVM内存管理:深刻JVM内存异常分析与调优》。) 框架

Java堆OOM测试 ide

/**
 * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM
{
    static class OOMObject
    {
    }

    public static void main( String[] args )
    {
        java.util.List<OOMObject> list = new ArrayList<OOMObject>();
        while( true )
        {
            list.add( new OOMObject() );
        }
    }
}

运行结果: 测试

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid7372.hprof ...
Heap dump file created [24724537 bytes in 0.699 secs]
2.VM栈和本地方法栈

Hotspot虚拟机并不区分VM栈和本地方法栈,所以-Xoss参数其实是无效的,栈容量只由-Xss参数设定。关于VM栈和本地方法栈在VM Spec描述了两种异常:StackOverflowError与OutOfMemoryError,当栈空间没法继续分配分配时,究竟是内存过小仍是栈太大其实某种意义上是对同一件事情的两种描述而已,在笔者的实验中,对于单线程应用尝试下面3种方法均没法让虚拟机产生OOM,所有尝试结果都是得到SOF异常。 spa

1.使用-Xss参数削减栈内存容量。结果:抛出SOF异常时的堆栈深度相应缩小。
2.定义大量的本地变量,增大此方法对应帧的长度。结果:抛出SOF异常时的堆栈深度相应缩小。 
3.建立几个定义不少本地变量的复杂对象,打开逃逸分析和标量替换选项,使得JIT编译器容许对象拆分后在栈中分配。结果:实际效果同第二点 操作系统

清单2:VM栈和本地方法栈OOM测试(仅做为第1点测试程序) 线程

/**
 * VM Args:-Xss128k
 * 
 */
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;
        }
    }
}
运行结果
stack length:3155
Exception in thread "main" java.lang.StackOverflowError
	at memoryManagement.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
	at memoryManagement.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
若是在多线程环境下,不断创建线程却是能够产生OOM异常,可是基本上这个异常和VM栈空间够不够没有直接关系,甚至是给每一个线程的VM栈分配的内存越多反而越容易产生这个OOM异常。

缘由其实很好理解,操做系统分配给每一个进程的内存是有限制的,譬如32位Windows限制为2G,Java堆和方法区的大小JVM有参数能够限制最大值,那剩余的内存为2G(操做系统限制)-Xmx(最大堆)-MaxPermSize(最大方法区),程序计数器消耗内存很小,能够忽略掉,那虚拟机进程自己耗费的内存不计算的话,剩下的内存就供每个线程的VM栈和本地方法栈瓜分了,那天然每一个线程中VM栈分配内存越多,就越容易把剩下的内存耗尽。  code

清单3:建立线程致使OOM异常 对象

/**
 * VM Args:-Xss2M (这时候不妨设大些)
 */
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

特别提示一下,若是读者要运行上面这段代码,记得要存盘当前工做,上述代码执行时有很大令操做系统卡死的风险。

3.运行时常量池

要在常量池里添加内容,最简单的就是使用String.intern()这个Native方法。因为常量池分配在方法区内,咱们只须要经过-XX:PermSize和-XX:MaxPermSize限制方法区大小便可限制常量池容量。实现代码以下

清单4:运行时常量池致使的OOM异常

/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 * 
 */
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.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClassCond(Unknown Source)

4.方法区

方法区用于存放Class相关信息,因此这个区域的测试咱们借助CGLib直接操做字节码动态生成大量的Class,值得注意的是,这里咱们这个例子中模拟的场景其实常常会在实际应用中出现:当前不少主流框架,如Spring、Hibernate对类进行加强时,都会使用到CGLib这类字节码技术,当加强的类越多,就须要越大的方法区用于保证动态生成的Class能够加载入内存。

清单5:借助CGLib使得方法区出现OOM异常 

/**
 * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 *
 */
public class JavaMethodAreamOOM
{
    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)        
       at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)        
       at java.lang.ClassLoader.defineClass(ClassLoader.java:616)        
       ... 8 more

5. 本机直接内存

DirectMemory容量可经过-XX:MaxDirectMemorySize指定,不指定的话默认与Java堆(-Xmx指定)同样,下文代码越过了DirectByteBuffer,直接经过反射获取Unsafe实例进行内存分配(Unsafe类的getUnsafe()方法限制了只有引导类加载器才会返回实例,也就是基本上只有rt.jar里面的类的才能使用),由于DirectByteBuffer也会抛OOM异常,但抛出异常时实际上并无真正向操做系统申请分配内存,而是经过计算得知没法分配既会抛出,真正申请分配的方法是unsafe.allocateMemory()

/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 *
 */
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 org.fenixsoft.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)
相关文章
相关标签/搜索