这两天遇到Tomcat 报java.lang.OutOfMemoryError: PermGen space 的问题,解决办法分享给大家!
1. PermGen space 是什么
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。
SUN JVM分代垃圾回收器把堆空间分成3块:
- Young Gen:年轻代,包括1个Eden区和2个Suvivor区,新创建的对象(大部分为短周期的对象)将进入这个区,虚拟机会频繁地对这个区进行垃圾回收。
- Old Gen:年老代,当对象在Young Gen呆地足够久(经过几次的垃圾回收仍然存在)或Young Gen空间不足时,对象将进入Old Gen,由于一般是生命周期比较长的对象,因此虚拟机对这块内存的回收频度会比较低,一旦回收,使用的将是一个耗时的Full GC,另外,一旦堆空间不足时,虚拟机也会尝试去回收这个区。
- Perm Gen:持久代,一些常量定义和类、方法声明及其bytecode都会放在这个区
2.通常什么情况下出现PermGen space
OutOfMemoryError: PermGen space从表面上看就是内存益出。说说为什么会内存益出:这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。
3. visualvm、jstat 分析jvm工具
jvisualvm
jvisualvm:监控内存泄露,跟踪垃圾回收,执行时内存、cpu分析,线程分析...
在JDK_HOME/bin目录下面,有一个jvisualvm.exe文件,双击打开
监控项总共分为概述,监视,线程和一个抽样。
- 概述包括 (jvm启动参数,系统参数)
如果选择Eclipse还可以可以看到eclipase 的启动参数;
- 监视
左上:cpu利用率,gc状态的监控
右上:堆利用率,永久内存区的利用率
左下:类的监控
右下:线程的监控
performGC:gc的详细运行状态
HeapDump:堆的详细状态(可以看到堆的概况,里面所有的类,还能点进具体的一个类查看这个类的状态)
- 线程
能够显示线程的名称和运行的状态,在调试多线程时必不可少,而且可以点进一个线程查看这个线程的详细运行情况
参考:http://blog.youkuaiyun.com/a19881029/article/details/8432368
http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/
Jstat
Jstat 是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。可见,Jstat是轻量级的、专门针对JVM的工具,非常适用。
可以列出当前JVM版本支持的选项,常见的有
- l class (类加载器)
- l compiler (JIT)
- l gc (GC堆状态)
- l gccapacity (各区大小)
- l gccause (最近一次GC统计和原因)
- l gcnew (新区统计)
- l gcnewcapacity (新区大小)
- l gcold (老区统计)
- l gcoldcapacity (老区大小)
- l gcpermcapacity (永久区大小)
- l gcutil (GC统计汇总)
- l printcompilation (HotSpot编译统计)
参考:http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstat.html#class_option
http://nassir.iteye.com/blog/1517484
4.解决内存溢出的问题
1.手动设置MaxPermSize的大小, TOMCAT_HOME/bin/catalina.bat(Linux上为catalina.sh)文件
set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG% -Xms256m -Xmx512m -XX:MaxNewSize=256m -XX:MaxPermSize=256m
常见配置汇总
堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值.如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值.注意Survivor区有两个.如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数.并行收集线程数.
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比.公式为1/(1+n)
并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式.适用于单CPU情况.
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数.并行收集线程数.
2.检查代码(不多说了)
3.Tomcat便于开发者开发,tomcat支持 热加载,但是当热加载次数变多也会造成内存溢出(也是本宝宝此次遇到的问题!~~~~(>_<)~~~~)
原因是由于运行项目的classes目录文件变更引起tomcat reload,同时reload时没有释放资源引起的宕机。
当您重新部署您的应用程序时,Tomcat 将创建新的类加载器。旧类加载器必须是垃圾回收 ,但是tomcat 每次热加载GC(Garbage Collection)不会在主程序运行期对 PermGen space进行清理 ,导致每次都又重新 加载一次所有的 class,最后加载的多了,就出现了PermGen space;
现象:用VisualVM时候,PermGen已经满了达到最大值了,类区的装入类总数达到了应用正常类的好多倍!最后程序直接挂掉!
这种情况下无论你MaxPermSize改变的多大都不管用的,解决办法修改tomcat 的server.xml文件,让项目变更时不reload。
reloadable 改成 false; 就这么简单! tomcat 的坑啊!
- 这是应该用的不多吧,网上看到的,并且试了试 是好用的,只是程序响应比较慢,解决的不是本质的问题,但是还是有必要知道下,原理我并不懂,衰!
参考地址:http://www.tuicool.com/articles/6BJJzin
5.tomcat 的 classload 机制
网上文章比较多,不写了!哈哈,好懒!
最后看到JVM8 已经没有PermGen 了!参考:Java PermGen 去哪里了?
参考地址:http://ifeve.com/java-permgen-removed/
参考书籍:
1、分布式java应用基础
2、深入理解JVM