引入:

上文中我们用MAT工具去分析了关于堆内存的很多细节问题,而内存除了堆内存以外还有非堆内存(包括栈和方法区),而方法区PermGen,它主要存放了两种东西,一种是被ClassLoader加载的类,一种是字符串常量,我们这篇文章着重分析PermGen上被ClassLoader所加载的类。

实践:

思路:

因为OutOfMemoryError时会生成HeapDump文件

(通过命令行参数 -XX:+HeapDumpOnOutOfMemoryError) ,而这个HeapDump文件不仅会包含堆信息,而且会包含Perm上的被ClassLoader所加载的Class的信息,所以我们实践的思想就是先构造一个场景,通过无限循环让无穷多个ClassLoader加载类,从而让PermGen OOM,然后借助工具分析HeapDump。

准备:

为此,我们加入以下命令行选项:

我这里分别解释下:

-XX:PermSize=16M-XX:MaxPermSize=32M的目的是让Perm尽可能设置小,从而缩短我们做实验的时间。

-XX:+HeapDumpOnOutOfMemoryError的目的是为了在OOM时候生成一个heapDump文件,而这个文件我们是可以通过工具来分析Perm上的被加载的类的信息的。

-XX:-ClassUnloading的目的是让HotSpot VM在做Full GC时候不会对PermGen上的加载的类进行GC,从而缩短做实验时间 (也就是让被加载的类占用的内存只增不减,从而更容易OOM)

-verbose 不用多说了,让我们能看到执行,从而知道ClassLoader正在加载类

代码:

按照上面思路,我们写了一个程序:

/**     * 让无限多个Classloader去加载某个类,因为不同的ClassLoader加载的类是不同的,所以加载后的Class会放在     * 内存的Perm区,从而最终会导致内存溢出,我们可以吧内存Perm区设置小点,然后加载一个比较大的类(比如多于1000行)     * 来缩短我们的实验时间     * @throws Exception     */    public static void makePermOutOfMemory1() throws Exception{                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               //用URLClassloader可以加载任意目录/JAR包下的类的字节码文件        String path="D:/Charles/Workspace2/MemoryLeakResearch/classloaded/commons-codec-1.4.jar";        URL url= new File(path).toURL();          URL[] urls = {url};                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               //无限创建新的URLClassLoader,并且让其加载指定的一个大类        while(true){                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       URLClassLoader cl = new URLClassLoader(urls);            cl.loadClass("org.apache.commons.codec.binary.Base64");           }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                }

程序很简单,就是在无限循环中创建无数多个URLClassLoader,然后让他们去加载commons-codec-1.4.jar包中的Base64类,这个类有1000多行,算是个比较大的类,所以导致OOM发生不会花费太多时间。

这里我们用URLClassLoader的目的是它可以加载任意目录位置或者JAR包中的类,我们只是为了展示这个代码的通用性,而不要让类局限于本项目。

结果:

运行了十几秒钟后,果然OOM 发生了:

和我们设想的一样,PermGen 溢出,并且生成了一个heap dump文件。

分析结果:

我们依然用MAT工具来分析这个heap dump文件,在"Java Basics"->"Class Loader Explorer"下,我们看到了PermGen中被各个类加载器中加载的类的情况:

从这里可以看出,这里有 2048-3=2045个java.net.URLClassLoader 被创建,这就是我们在无限循环中无限创建的URLClassLoader,每个ClassLoader载入了5个类,而被加载的类都放在了PermGen上,所以导致了PermGen的OutOfMemory.

我们验证下,随便打开某个java.net.URLClassLoader:

可以发现的确每个ClassLoader加载了5个类,分别是BinaryDecoder,BinaryEncoder,Decoder,Encoder,Base64 .

结合我们自己写的代码,我们是显式的让新建的URLClassLoader去loadClass这个Base64类的:

URLClassLoader cl = new URLClassLoader(urls);            cl.loadClass("org.apache.commons.codec.binary.Base64");

而Base64类会实现接口BinaryDecoder和BinaryEncoder:

所以同一个URLClassLoader也会去加载BinaryDecoder和BinaryEncoder。

而BinaryDecoder接口是继承自Decoder接口的,BinaryEncoder接口是继承自Encoder接口的:

所以同一个URLClassLoader也会去加载Decoder和Encoder.

总结:

(1)PermGen是JAVA规范中的”方法区”,它主要放两部分内容,一块是被Classloader加载的类,另一块是字符串常量区。

(2)PermGen的ClassLoader加载的类信息,会放入heap dump文件中,而字符串常量的信息,不会放入heap dump文件中。

(3)默认HotSpot VM在做Full GC时候,回收的是Heap上的内存+PermGen上的加载的类+PermGen上的字符串常量,但是如果配置了vm参数 -XX:-ClassUnloading后 ,则只回收Heap上的内存+PermGen上的字符串常量

(3)对于Classloader加载类顺序,符合“双亲委派”的加载链模式。