阅读原文
*现在,网上关于讨论PermGen OOM的资料很多,但是深入分析PermGen区域内存溢出原因的资料很少。本篇文章尝试全面分析一下PermGen OOM的原因,其中涉及到了Java虚拟机运行时数据区、类型装载、类型卸载等,测试代码涉及到了JMX协议。
【知识准备】
本部分将对Java虚拟机运行时数据区做一个简单的介绍,着重说明PermGen区域(永久存储区)存放的内容,并对运行时数据区的访问方式做一个归纳说明,为后面深入分析类型卸载和PermGen OOM做铺垫。为了更具有通用性,本部分将更多关注虚拟机协议本身,可能和具体的虚拟机实现有少许的出入。
Java虚拟机的运行时数据区一般分类如下(不一定是物理划分):
方法区可以简单的等价为所谓的PermGen区域(永久存储区),在很多虚拟机相关的文档中,也将其称之为"永久堆"(permanent heap),作为堆空间的一部分存在。介于此,我们可以简单说明一下我们常用的几个堆内存配置的参数关系:
*-XX: PermSize:*永久堆(Pergen区域)大小默认值
*-XX:MaxPermSize:*永久堆(Pergen区域)最大值
*-Xms:*堆内存大小默认值
*-Xmx:*堆内存最大值
从开发者角度,虚拟机运行时数据区的访问方式简要归纳如下:
【示意图说明:】
在本部分,首先交代一下必要的前提知识,这也为理解后面的测试程序做铺垫。
【测试程序说明】
【测试程序一:模拟PermGen OOM】
try {
//准备url
URL url = new File("D:/classes").toURL();
URL[] urls = {url};
//获取有关类型加载的JMX接口
ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
//用于缓存类加载器
List
while (true) {
//加载类型并缓存类加载器实例
ClassLoader classLoader = new URLClassLoader(urls);
classLoaders.add(classLoader);
classLoader.loadClass("ZhuXing");
//显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
System.out.println("active: " + loadingBean.getLoadedClassCount());
System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
}
} catch (Exception e) {
e.printStackTrace();
}
【测试程序一分析】
运行测试程序一,输出信息如下(摘取了部分):
......
[Loaded ZhuXing from [file:/D:/classes/]]
total: 2914
active: 2914
unloaded: 0
[Loaded ZhuXing from [file:/D:/classes/]]
total: 2915
active: 2915
unloaded: 0
[Full GC 4852K->4852K(8720K), 0.0993780 secs]
[Full GC 4852K->4829K(8720K), 0.0999775 secs]
[Full GC 4829K->4829K(8720K), 0.0989805 secs]
[Full GC 4829K->4829K(8720K), 0.0997261 secs]
......
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
......
[Unloading class ZhuXing]
......
[Loaded java.lang.Shutdown from D:\eos6\jdk1.5.0_09\jre\lib\rt.jar]
[Loadedjava.lang.Shutdown$Lockfrom D:\eos6\jdk1.5.0_09\jre\lib\rt.jar
针对以上摘录的虚拟机器运行时信息,分析结论如下:
【测试程序二:PermGen区域垃圾收集】
和测试程序一相比,删除了类加载器实例缓存的代码。
try {
//准备url
URL url = new File("D:/classes").toURL();
URL[] urls = {url};
//获取有关类型加载的JMX接口
ClassLoadingMXBean loadingBean = ManagementFactory.getClassLoadingMXBean();
while (true) {
//加载类型,不缓存类加载器实例
new URLClassLoader(urls).loadClass("ZhuXing");
//显示数量信息(共加载过的类型数目,当前还有效的类型数目,已经被卸载的类型数目)
System.out.println("total: " + loadingBean.getTotalLoadedClassCount());
System.out.println("active: " + loadingBean.getLoadedClassCount());
System.out.println("unloaded: " + loadingBean.getUnloadedClassCount());
}
} catch (Exception e) {
e.printStackTrace();
}
【测试程序二分析】
运行测试程序二很长时间,一直没有发生PermGen OOM异常,输出信息如下(摘取了部分):
...
[Loaded ZhuXing from [file:/D:/classes/]]
total: 19540
active: 1052
unloaded: 18488
[Full GC 1563K->259K(2112K), 0.1758958 secs]
......
[Unloading class ZhuXing]
[Unloading class ZhuXing]
[Unloading class ZhuXing]
......
[GC 1968K->1563K(2112K), 0.0025266 secs]
......
[Loaded ZhuXing from [file:/D:/classes/]]
total: 21098
active: 440
unloaded: 20658
...
针对以上摘录的虚拟机器运行时信息,分析结论如下:
通过上面的测试程序分析,我们发现PermGen OOM发生的原因和类型装载、类型卸载有直接的关系,可以对PermGen OOM发生的原因做如下大致的总结:
通过前面的讨论,我们知道如果加载某种类型的类加载器实例没有处于unreachable状态,则该类型就不会被卸载,该类型不被卸载,则对应的类型信息在PermGen区域中占有的堆内存就不会被释放。下面,针对典型的Java应用分类,分析一下常用类加载器加载的类型被下载的可能性。
【普通Java应用】
系统类加载器:由于其负责加载虚拟机的核心类型,所以由其加载的类型在整个程序运行期间不可能被卸载,对应类型信息占用的PermGen区域堆空间不可能得到释放。
扩展类加载器:负责加载JDK扩展路径下的类型,扩展类加载器同时又作为系统类加载器的父类加载器,所以,由其加载的类型在整个程序运行期间基本上不可能被卸载,对应类型信息占用的PermGen区域堆空间基本不可能得到释放。
系统类加载器:负责加载程序类路径上面的类型,由其加载的类型在整个程序运行期间基本上不可能被卸载,对应类型信息占用的PermGen区域堆空间基本不可能得到释放。
用户自定义类加载器:对于其加载的类型,满足类型卸载要求的可能性比较容易控制,只要是其实例本身处于unreachable状态,其加载的类型会被卸载,PermGen区域中对应的空间占有也会被释放。
【插件开发】
系统类加载器:由于其负责加载虚拟机的核心类型,所以由其加载的类型在插件应用运行期间不可能被卸载,对应类型信息占用的PermGen区域堆空间不可能得到释放。
插件类加载器:系统插件类加载器负责加载OSGI实现的相关类型,所以由其加载的类型在插件应用运行期间不可能被卸载;用户开发的插件所使用的默认插件类加载器,和特定的插件本身进行域绑定,插件之间存在一定的类型引用关系,并且特定插件在整个插件应用的运行时被停止的可能性也很小,所以类型卸载发生几率极小。
用户自定义类加载器:对于其加载的类型,满足类型卸载要求的可能性比较容易控制,只要是其实例本身处于unreachable状态,其加载的类型会被卸载,PermGen区域中对应的空间占有也会被释放。
通过上面的PermGen OOM的原因的分析,不难看出对应的应对措施:
通过设置合理的XX: PermSize和-XX:MaxPermSize参数值是减少和有效避免PermGen OOM发生的最有效最主要的措施,尤其是针对普通用户而言,这基本上是唯一的办法。关于合理设置这两个参数,建议如下:
此部分的建议可以作为开发者进行性能调优或者日常开发时候的参考,尽量能够配合相应的性能监控工具进行:
【后记】
写这篇文章的初衷是为了深入的分析PermGen OOM发生的原因,在深入分析的基础之上理解PermGen OOM的应对措施,从"为什么会发生PermGen OOM"到"到底为什么会发生PermGen OOM"。希望对大家更深入的认识PermGen OOM和PermGen OOM的应对措施起到作用,谢谢!