Tomcat给我的java.lang.OutOfMemoryError: PermGen
今天,Tomcat给了我这么一个异常:java.lang.OutOfMemoryError:PermGen space。自己是第一次遇到,抱着好奇的心情google了一下,居然是个很常见的异常!故记录一下。:)
原因:
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGenspace进行清理,所以如果你的应用中有很CLASS的话,就很可能出现PermGen space错误,这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小超过了jvm默认的大小(4M)那么就会产生此错误信息了。
解决方法:
1. 手动设置MaxPermSize大小
修改TOMCAT_HOME/bin/catalina.bat(Linux下为catalina.sh),在“echo "UsingCATALINA_BASE: $CATALINA_BASE"”上面加入以下行:
set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=128M -XX:MaxPermSize=512m
catalina.sh下为:
JAVA_OPTS="$JAVA_OPTS -server -XX:PermSize=128M -XX:MaxPermSize=512m"
2. 将相同的第三方jar文件移置到tomcat/shared/lib目录下,这样可以达到减少jar 文档重复占用内存的目的。
如果遇到这个异常:java.lang.OutOfMemoryError: Java heap space 是什么原因呢?
解释:
Heap size 设置
JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。
提示:在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。
提示:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。
解决方法:
手动设置Heap size
修改TOMCAT_HOME/bin/catalina.bat,在“echo "UsingCATALINA_BASE: $CATALINA_BASE"”上面加入以下行:
set JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:MaxNewSize=256m
或修改catalina.sh
在“echo "UsingCATALINA_BASE: $CATALINA_BASE"”上面加入以下行:
JAVA_OPTS="$JAVA_OPTS -server -Xms800m -Xmx800m -XX:MaxNewSize=256m"
另外看到了另外一个帖子,觉得挺好,摘抄如下:
主题: 分析java.lang.OutOfMemoryError:PermGen space
SUN JDK+Tomcat 5.5.20运行服务的时候遇到问题,服务器跑几天后就会挂掉,并报java.lang.OutOfMemoryError: PermGen space异常。
发现很多人把问题归因于:spring,hibernate,tomcat,因为他们动态产生类,导致JVM中的permanent heap溢出。然后解决方法众说纷纭,有人说升级 tomcat版本到最新甚至干脆不用tomcat。还有人怀疑spring的问题,在spring论坛上讨论很激烈,因为spring在AOP时使用CBLIB会动态产生很多类。
但问题是为什么这些王牌的开源会出现同一个问题呢,那么是不是更基础的原因呢?tomcat在Q&A很隐晦的回答了这一点,我们知道这个问题,但这个问题是由一个更基础的问题产生。
于是有人对更基础的JVM做了检查,发现了问题的关键。原来SUN 的JVM把内存分了不同的区,其中一个就是permenter区用来存放用得非常多的类和类描述。本来SUN设计的时候认为这个区域在JVM启动的时候就固定了,但他没有想到现在动态会用得这么广泛。而且这个区域有特殊的垃圾收回机制,现在的问题是动态加载类到这个区域后,gc根本没办法回收!
2003年的时候就有一个bug报告给sun,但是到现在,这个bug还没有close!有人在这个bug加了句评语:“A bug this critical is open since 2003?Absolutely shameful.” 我觉得SUN在这个BUG上确实有些丢脸。
对这个bug最彻底的解决办法就是不要用SUN的JDK,而改用BEA的 JRokit.
打不过,还逃不过吗?有众多的选择,这就是开源的好。:)
最后,给出几篇文章供大家学习参考:
Java虚拟机的选项:http://www.tot.name/show/3/7/20061112220131.htm
Hot Spot JVM5中的GC调优:http://www.tot.name/show/3/7/20061112220201.htm
引言
有JAVA开发经验的朋友们一定碰到过下面的这种情况,那就是自己所开发的应用运行了一段时间后其性能或者响应速度会有明显的降低.这是由多方面的原因造成的即有程序本身的优化问题,也有运行环境问题.此运行环境即包括硬件环境也包括软件环境.大多数人第一个能想到的解决方法是提升硬件的配置而忽略了程序本身的运行环境JVM也提供了比较多的调优选项.本文将重点描述利用JVM的一些选项对GC进行调优.
约定:
1.读者应具备一定JAVA的知识.
2.本文中的JVM选项均以SUN公司发布的HotSpot JVM 5为准(不过大多数的选项在JVM1.3,JVM1.4中也是可用的).
3.以JAVA_HOME下demo/jfc/SwingSet2/SwingSet2.jar为例进行说明.
4.阅读本文需要一些关于GC的知识,可以到附录A中了解这些知识。
关键字:
JVM(java虚拟机),调优,GC(垃圾回收)
JVM GC调优
为了能够将JVM GC的调优能够使用在具体的实践当中,下面将利用若干个例子来说明GC的调优.
例1:Heap size 设置
JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。
当在JAVA_HOME下demo/jfc/SwingSet2/目录下执行下面的命令。
java -jar -Xmn4m -Xms16m -Xmx16m SwingSet2.jar
系统输出为:
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError:Java heap space
Exception in thread "Image Fetcher 3" java.lang.OutOfMemoryError:Java heap space
Exception in thread "Image Fetcher 1" java.lang.OutOfMemoryError:Java heap space
Exception in thread "Image Fetcher 2" java.lang.OutOfMemoryError:Java heap space
除了这些异常信息外,还会发现程序的响应速度变慢了。这说明Heap size 设置偏小,GC占用了更多的时间,而应用分配到的执行时间较少。
提示:在JVM中如果98%的时间是用于GC且可用的Heap size 不足2%的时候将抛出此异常信息。
将上面的命令换成以下命令执行则应用能够正常使用,且未抛出任何异常。
java -jar -Xmn4m -Xms16m -Xmx32m SwingSet2.jar
提示:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。
例2:Young Generation(-Xmn)的设置
在本例中看一下Young Generation的设置不同将有什么现象发生。
假设将Young generation 的大小设置为4M ,即执行java -jar -verbose:gc -Xmn4m -Xms32m-Xmx32m -XX:+PrintGCDetails SwingSet2.jar,屏幕输出如下(节选)
[GC [DefNew: 3968K->64K(4032K), 0.0923407 secs] 3968K->2025K(32704K),0.0931870 secs]
[GC [DefNew: 4021K->64K(4032K), 0.0356847 secs] 5983K->2347K(32704K),0.0365441 secs]
[GC [DefNew: 3995K->39K(4032K), 0.0090603 secs] 6279K->2372K(32704K),0.0093377 secs]
[GC [DefNew: 3992K->23K(4032K), 0.0057540 secs] 6325K->2356K(32704K),0.0060290 secs]
[GC [DefNew: 3984K->27K(4032K), 0.0013058 secs] 6317K->2360K(32704K),0.0015888 secs]
[GC [DefNew: 3981K->59K(4032K), 0.0023307 secs] 6315K->2422K(32704K),0.0026091 secs]
将程序体制并将Young Generation的大小设置为8M,即执行java -jar -verbose:gc -Xmn8m -Xms32m-Xmx32m -XX:+PrintGCDetails SwingSet2.jar,屏幕输出如下(节选)
[GC [DefNew: 7808K->192K(8000K), 0.1016784 secs] 7808K->2357K(32576K), 0.1022834secs]
[GC [DefNew: 8000K->70K(8000K), 0.0149659 secs] 10165K->2413K(32576K),0.0152557 secs]
[GC [DefNew: 7853K->59K(8000K), 0.0069122 secs] 10196K->2403K(32576K),0.0071843 secs]
[GC [DefNew: 7867K->171K(8000K), 0.0075745 secs] 10211K->2681K(32576K),0.0078376 secs]
[GC [DefNew: 7970K->192K(8000K), 0.0201353 secs] 10480K->2923K(32576K),0.0206867 secs]
[GC [DefNew: 7979K->30K(8000K), 0.1787079 secs] 10735K->4824K(32576K),0.1790065 secs]
那么根据GC输出的信息(这里取第一行)做一下Minor收集的比较。可以看出两次的Minor收集分别在Young generation中找回3904K(3968K->64K)和7616K(7808K->192K)而对于整个jvm则找回1943K(3968K->2025)和5451K(7808K->2357K)。第一种情况下Minor收集了大约50%(1943/3904)的对象,而另外的50%的对象则被移到了tenured generation。在第二中情况下Minor收集了大约72%的对象,只有不到30%的对象被移到了Tenured Generation.这个例子说明此应用在的Young generation 设置为4m时显的偏小。
提示:一般的Young Generation的大小是整个Heap size的1/4。Young generation的minor收集率应一般在70%以上。当然在实际的应用中需要根据具体情况进行调整。
例3:Young Generation对应用响应的影响
还是使用-Xmn4m 和-Xmn8m进行比较,先执行下面的命令
java -jar -verbose:gc -Xmn4m -Xms32m-Xmx32m -XX:+PrintGCDetails -XX:+PrintGCApplicationConcurrentTime-XX:+PrintGCApplicationStoppedTime SwingSet2.jar
屏幕输出如下(节选)
Application time: 0.5114944 seconds
[GC [DefNew: 3968K->64K(4032K), 0.0823952 secs] 3968K->2023K(32704K),0.0827626 secs]
Total time for which application threads were stopped: 0.0839428 seconds
Application time: 0.9871271 seconds
[GC [DefNew: 4020K->64K(4032K), 0.0412448 secs] 5979K->2374K(32704K),0.0415248 secs]
Total time for which application threads were stopped: 0.0464380 seconds
Young Generation 的Minor收集占用的时间可以计算如下:应用线程被中断的总时常/(应用执行总时?L+应用线程被中断的总时常),那么在本例中垃圾收集占用的时?L约为系统的5%~14%。那么当垃圾收集占用的时间的比例越大的时候,系统的响应将越慢。
提示:对于互联网应用系统的响应稍微慢一些,用户是可以接受的,但是对于GUI类型的应用响应速度慢将会给用户带来非常不好的体验。
例4:如何决定Tenured Generation 的大小
分别以-Xmn8m -Xmx32m和-Xmn8m -Xmx64m进行对比,先执行
java -verbose:gc -Xmn8m -Xmx32m-XX:+PririntGCDetails -XX:+PrintGCTimeStampsjava类,命令行将提示(只提取了Major收集)
111.042: [GC 111.042: [DefNew:8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured:18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306secs]
122.463: [GC 122.463: [DefNew: 8128K->8128K(8128K), 0.0000560 secs]122.463:[Tenured: 18630K->2366K(24576K), 0.1322560 secs] 26758K->2366K(32704K),0.1325284 secs]
133.896: [GC 133.897: [DefNew: 8128K->8128K(8128K), 0.0000443 secs]133.897:[Tenured: 18240K->2573K(24576K), 0.1340199 secs] 26368K->2573K(32704K),0.1343218 secs]
144.112: [GC 144.112: [DefNew: 8128K->8128K(8128K), 0.0000544 secs]144.112:[Tenured: 16564K->2304K(24576K), 0.1246831 secs] 24692K->2304K(32704K),0.1249602 secs]
再执行java -verbose:gc-Xmn8m -Xmx64m-XX:+PririntGCDetails -XX:+PrintGCTimeStamps java类,命令行将提示(只提取了Major收集)
90.597: [GC 90.597: [DefNew: 8128K->8128K(8128K), 0.0000542 secs]90.597:[Tenured: 49841K->5141K(57344K), 0.2129882 secs] 57969K->5141K(65472K),0.2133274 secs]
120.899: [GC 120.899: [DefNew: 8128K->8128K(8128K), 0.0000550 secs]120.899:[Tenured: 50384K->2430K(57344K), 0.2216590 secs] 58512K->2430K(65472K),0.2219384 secs]
153.968: [GC 153.968: [DefNew: 8128K->8128K(8128K), 0.0000511 secs]153.968:[Tenured: 51164K->2309K(57344K), 0.2193906 secs] 59292K->2309K(65472K),0.2196372 secs]
可以看出在Heap size 为32m的时候系统等候时间约为0.13秒左右,而设置为64m的时候等候时间则增大到0.22秒左右了。但是在32m的时候系统的Major收集间隔为10秒左右,而Heap size 增加到64m的时候为30秒。那么应用在运行的时候是选择32m还是64m呢?如果应用是web类型(即要求有大的吞吐量)的应用则使用64m(即heapsize大一些)的比较好。对于要求实时响应要求较高的场合(例如GUI型的应用)则使用32m比较好一些。
注意:
1。因为在JVM5运行时已经对Heap-size进行了优化,所以在能确定java应用运行时不会超过默认的Heap size的情况下建议不要对这些值进行修改。
2。Heap size的 -Xms -Xmn 设置不要超出物理内存的大小。否则会提示“Error occurred duringinitialization of VM Could not reserve enough space for object heap”。
例5:如何缩短minor收集的时间
下面比较一下采用-XX:+UseParNewGC选项和不采用它的时候的minor收集将有什么不同。先执行
java -jar -server -verbose:gc -Xmn8m -Xms32m -Xmx32m SwingSet2.jar
系统将输出如下信息(片段〕
[GC 7807K->2641K(32576K), 0.0676654 secs]
[GC 10436K->3108K(32576K), 0.0245328 secs]
[GC 10913K->3176K(32576K), 0.0072865 secs]
[GC 10905K->4097K(32576K), 0.0223928 secs]
之后再执行 java -jar -server-verbose:gc -XX:+UseParNewGC -Xmn8m -Xms32m -Xmx32m SwingSet2.jar
系统将输出如下信息(片段〕
[ParNew 7808K->2656K(32576K), 0.0447687 secs]
[ParNew 10441K->3143K(32576K), 0.0179422 secs]
[ParNew 10951K->3177K(32576K), 0.0031914 secs]
[ParNew 10985K->3867K(32576K), 0.0154991 secs]
很显然使用了-XX:+UseParNewGC选项的minor收集的时间要比不使用的时候优。
例6:如何缩短major收集的时间
下面比较一下采用-XX:+UseConcMarkSweepGC选项和不采用它的时候的major收集将有什么不同。先执行
java -jar -verbose:gc -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -Xmn64m -Xms256m-Xmx256m SwingSet2.jar
系统将输出如下信息(片段〕
[Full GC 22972K->18690K(262080K), 0.2326676 secs]
[Full GC 18690K->18690K(262080K), 0.1701866 secs
之后再执行 java -jar-verbose:gc -XX:+UseParNewGC -Xmn64m -Xms256m -Xmx256m SwingSet2.jar
系统将输出如下信息(片段〕
[Full GC 56048K->18869K(260224K), 0.3104852 secs]
提示:此选项在Heap Size 比较大而且Major收集时间较长的情况下使用更合适。
例7:关于-server选项 在JVM中将运行中的类认定为server-class的时候使用此选项。SUN 的Hot Spot JVM5 如果判断到系统的配置满足如下条件则自动将运行的类认定为server-class,并且会自动设置jvm的选项(当没有手工设置这选项的时候〕而且HOTSPOT JVM5提供了自动调优的功能,他会根据JVM的运行情况进行调整。如果没有特别的需要是不需要太多的人工干预的。SUN形象的称这个机制为“人体工学”(Ergonomics〕。具体可以参考http://java.sun.com/docs/hotspot/gc5.0/ergo5.html
*.具有2个或更多个物理的处理器
*.具有2G或者更多的物理内存
提示:此选项要放在所有选项的前面。例如:java -server 其他选项 java类
附录A:预备知识
.JVM中对象的划分及管理
JVM根据运行于其中的对象的生存时间大致的分为3种。并且将这3种不同的对象分别存放在JVM从系统分配到的不同的内存空间。这种对象存放空间的管理方式叫做Generation管理方式。
1。Young Generation:用于存放“早逝”对象(即瞬时对象)。例如:在创建对象时或者调用方法时使用的临时对象或局部变量。
2。Tenured Generation:用于存放“驻留”对象(即较长时间被引用的对象)。往往体现为一个大型程序中的全局对象或长时间被使用的对象。
3。Perm Generation:用于存放“永久”对象。这些对象管理着运行于JVM中的类和方法。
.JVM选项的分类
JVM有这么几种选项供使用.
1.供-X选项使用的项目,又称为非标准选项,不同厂商的此类型选项是有所不同的。例如:IBM的JVM用的一些选项在Sun的JVM中就不一定能生效。这种选项的使用方式如下:
java -Xmn16m -Xms64m -Xmx64m java类名
2.供-XX选项使用的项目,这种类型的选项可能要求有对系统信息访问的权限。所以要慎用。这种选项的使用方式如下:
java -XX:MaxHeapFreeRatio=70 -XX:+PrintGCDetails java类名
3.java选项(即在命令行执行java后提示的选项).
java -server -verbose:gc -d64 java类名
.垃圾收集分类
在JVM中有两种垃圾方式,一种叫做Minor(次收集),另一种叫做Major(主收集)。其中Minor在Young Generation的空间被对象全部占用后执行,主要是对Young Generation中的对象进行垃圾收集。而Major是针对于整个Heap size的垃圾收集。其中Minor方式的收集经常发生,并且Minor收集所占用的系统时间小。Major方式的垃圾收集则是一种“昂贵”的垃圾收集方式,因为在Major要对整个Heap size进行垃圾收集,这会使得应用停顿的时间变得较长。
.GC信息的格式
[GC [<collector>: <startingoccupancy1> -> <ending occupancy1>, <pause time1> secs]<starting occupancy3> -> <ending occupancy3>, <pausetime3> secs]
<collector> GC为minor收集过程中使用的垃圾收集器起的内部名称.
<starting occupancy1> young generation 在进行垃圾收集前被对象使用的存储空间.
<ending occupancy1> young generation 在进行垃圾收集后被对象使用的存储空间
<pause time1> minor收集使应用暂停的时间长短(秒)
<starting occupancy3> 整个堆(Heap Size)在进行垃圾收集前被对象使用的存储空间
<ending occupancy3> 整个堆(Heap Size)在进行垃圾收集后被对象使用的存储空间
<pause time3> 整个垃圾收集使应用暂停的时间长短(秒),包括major收集使应用暂停的时间(如果发生了major收集).
.GC信息的选项
-XX:+PrintGCDetails 显示GC的详细信息
-XX:+PrintGCApplicationConcurrentTime 打印应用执行的时间
-XX:+PrintGCApplicationStoppedTime 打印应用被暂停的时间
提示:1.":"后的"+"号表示开启此选项,如果是"-"号那么表示关闭此选项。
2.在不同的选项和不同的收集方式和类型下输出的格式会有所不同。
附录B:HotSpot JVM 选项
请参考JavaTM HotSpot VM Options
附录C:其他资源
http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html
http://java.sun.com/docs/hotspot/gc5.0/ergo5.html