SEARU.ORG
当前位置:SEARU.ORG > Linux 新闻 > 正文

JVM内存溢出(OutOfMemoryError异常)

  1.  Java堆

    java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么当对象数量达到最大堆容量限制后就会产生OutOfMemoryError异常。当出现java堆内存溢出时,异常堆栈信息“java.lang.OutOfMemoryError”会跟着进一步提示“Java heap space,如下:

    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 com.test.outofmemory.HeapOOM.main(HeapOOM.java:25)

    解决方式:

           堆的最小值:-Xms 如-Xms20m
            堆的最大值 -Xmx 如果设为一样的则可避免堆自动扩展。
            年轻代大小: -Xmn
            -XX:+HeapDumpOnOutOfMemoryError 当内存溢出时Dump出当前的内存堆转存快照。
            Eclipse中虚拟机参数设置:debug As–>open dubug dialog
            生成之后使用Eclipse Memory Analyzer 进行堆转储文件分析(需要安装MAT插件)。


    2.Java虚拟机栈和本地方法栈溢出

    关于java虚拟机栈和本地方法栈,在java虚拟机中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机允许的最大深度,将抛出StackOverflowError异常

  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常(OutOfMemoryError unable to cteate new native thread)。

    解决方式:

    -Xss:设置每条线程的Statck大小.在JDK1.5以后默认是1M,之前是256K

    抛出StackOverFlow异常:操作系统分配给每个线程的内存是有限的,机器总内存减去Xmx再减去MaxPermSize,程序计数器占内存很少忽略,剩下的内存被虚拟机栈和本地方法栈瓜分,每个线程分到的栈容量越大,分配的线程数就小。正常情况栈深度1000-2000没问题,如果是建立更多线程导致的内存溢出,在不能减少线程的情况下,只能通过减小Xmx和栈容量来换取更多线程。

    3.方法区和运行时常量池溢出

        如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。该方法的作用是:如果池中已经包含一 个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此 String对象的引用。由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接 限制其中常量池的容量,如代码所示,运行时常量池导致的内存溢出异常。

/**  * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm  */ 
 public class RuntimeConstantPoolOOM {   
     public static void main(String[] args) {     
         // 使用List保持着常量池引用,避免Full GC回收常量池行为     
         List<String> list = new ArrayList<String>();      
         // 10MB的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.String.intern(Native Method)
    at com.test.outofmemory.HeapOOM.main(HeapOOM.java:34)

    从运行结果中可以看到,运行时常量池溢出,在OutOfMemoryError后面跟随的提示信息是“PermGen space”,说明运行时常量池属于方法区(HotSpot虚拟机中的永久代)的一部分。

    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。对于这些区域的测试,基本的思路是运行时产生大量的类去填满方法区,直到溢出。

虽然直接使用Java SE API也可以动态产生类(如反射时的GeneratedConstructorAccessor和动态代理等),但在本次实验中操作起来比较麻烦。在代码清单2-8中,笔者借助CGLib直接操作字节码运行时生成了大量的动态类。

值得特别注意的是,我们在这个例子中模拟的场景并非纯粹是一个实验,这样的应用经常会出现在实际应用中:当前的很多主流框架,如Spring、 Hibernate,在对类进行增强时,都会使用到CGLib这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成的Class可以加载入内 存。另外,JVM上的动态语言(例如Groovy等)通常都会持续创建类来实现语言的动态性,随着这类语言的流行,也越来越容易遇到与代码清单2-8相似 的溢出场景。

代码清单2-8 借助CGLib使方法区出现内存溢出异常

/**
 * 
 * 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 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

方法区溢出也是一种常见的内存溢出异常,一个类要被垃圾收集器回收掉,判定条件是比较苛刻的。在经常动态生成大量Class的应用中,需要特别注意类的回 收状况。这类场景除了上面提到的程序使用了CGLib字节码增强和动态语言之外,常见的还有:大量JSP或动态产生JSP文件的应用(JSP第一次运行时 需要编译为Java类)、基于OSGi的应用(即使是同一个类文件,被不同的加载器加载也会视为不同的类)等。

解决方式:

-PermSize :方法区的初始容量,默认是物理内存的1/64

-MaxPermSize :最大方法区容量。

4.本地直接内存溢出

    并不是虚拟机运行时数据区的一部分。JDK1.4中引入了NIO类,引入了一种基于通道与缓冲区的I/O方式,可以使用Native直接分配堆外内存,避免了再Java堆和Native堆中来回复制数据。

不会受到Java堆内存限制,但会受到机器总内存的限制。

如果如法申请到足够的空间抛出OutOfMemoryError。

解决方式:-XX:MaxDirectMemorySize 本机直接内存大小,如果不指定,则与Xmx一样。





未经允许不得转载:SEARU.ORG » JVM内存溢出(OutOfMemoryError异常)

赞 (0)
分享到:更多 ()

评论 0