引入:
上文中我们用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加载类顺序,符合“双亲委派”的加载链模式。