美文网首页程序员Java 杂谈
Java内存溢出异常(下)

Java内存溢出异常(下)

作者: bdqfork | 来源:发表于2019-01-08 11:56 被阅读2次

    此篇是上一篇文章Java内存溢出异常(上)的续篇,没有看过的同学,可以先看一下上篇。本篇文章将介绍剩余的两个溢出异常:方法区和运行时常量池溢出。

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

    这部分为什么会放在一起呢?在Java内存区域与内存溢出异常这篇文章中我们说过,运行时常量池实际上属于方法区的一部分,所以就放在一起讨论。

    常量池溢出

    在讨论常量池的溢出之前,先说明一下String.intern()方法,该方法会检查字符串常量池中是否已经包含了一个等于此String对象的字符串,如果已经包含了,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。看过fastjson源代码的同学应该知道,该方法在fastjson这个json解析库中大量的出现,以提高json的解析速度。

    在JDK 1.6之前的版本中,常量池是分配在永久代的,可以通过-XX:PermSize和-XX:MaxPermSize参数来设置大小,从而间接限制其中常量池的容量。

    通过以上条件,我们可以轻易的复现这个异常,代码如下:

    public class RuntimeConstantPoolOOM {
    
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            int i = 0;
            while (true) {
                list.add(String.valueOf(i++).intern());
            }
        }
    }
    

    如果你的JDK环境是1.6版本之前的,你会得到如下运行结果:

    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
        ......
    

    如果你是JDK 1.7+,那么这段代码会无限的运行下去。因为在JDK 1.7之后对String.intern()方法进行了修改。

    继续看下面这段经典的代码:

    public class RuntimeConstantPoolOOM {
    
        public static void main(String[] args) {
            String str1 = new StringBuilder("计算机").append("软件").toString();
            System.out.println(str1.intern() == str1);
    
            String str2 = new StringBuilder("ja").append("va").toString();
            System.out.println(str2.intern() == str2);
        }
    }
    

    这段代码在JDK 1.6中运行,会得到两个false,而在JDK 1.7中运行,会得到一个true和一个false。

    出现差异的原因是因为,在JDK 1.6中,intern()方法会把首次遇到的字符串实例复制到常量池中,返回的也是常量池中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而JDK 1.7中的intern()实训不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过了,字符串中常量池中已经有它的引用了,不符合“首次”出现的原则。

    这就可以解释,为什么在JDK 1.7+版本之后不能用String.intern()方法使常量池溢出的原因了,intern()不会像JDK 1.6之前的版本一样无限复制实例到常量池中了。

    方法区溢出

    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。这部分的测试可以通过生成大量的类去填满方法区,直到溢出,可以借助CGLib直接操作字节码运行时生成大量的动态类,代码如下:

    public class JavaMethodAreaOOM {
    
        public static void main(final String[] args) {
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o, objects);
                    }
                });
                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)
        ...
    

    在经常动态生成大量Class的应用中,比如说使用CGLib这类字节码技术的时候,容易出现方法区溢出。所以说在使用这类技术编写框架时,要留意这方面导致的内存溢出。

    本机直接内存溢出

    本机直接内存的溢出主要与大量的直接操作内存的API有关,比如说NIO相关的API,也可以通过rt.jar中的类使用Unsafe的功能来复现这个异常。DirectMemory容量可通过-XX:MaxDirectMemorySize指定,主要代码如下:

    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)
        ...
    

    由DirectMemory导致的内存溢出,不会在Heap Dump文件中不会看见明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。

    相关文章

      网友评论

        本文标题:Java内存溢出异常(下)

        本文链接:https://www.haomeiwen.com/subject/jfyvrqtx.html