java.lang.OutOfMemoryError: Java heap space错误处理方案

java.lang.OutOfMemoryError: Java heap space错误处理方案

                                                                                                              

症状:

java.lang.OutOfMemoryError:Javaheapspace

===================================================

分析:

      在JVM中如果98%的时间是用于GC且可用的 Heap size 不足2%的时候将抛出此异常信息。

      JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。

解决方案:

    利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。

例如:java-jar-Xmn16m-Xms64m-Xmx128mMyApp.jar

     如果Heap Size设置偏小,除了这些异常信息外,还会发现程序的响应速度变慢了。GC占用了更多的时间,而应用分配到的执行时间较少。

    Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。Heap size的 -Xms -Xmn 设置不要超出物理内存的大小。否则会提示“Error occurred during initialization of VM Could not reserve enough space for object heap”。

For Eclipse:

      Eclipse 有启动参数里设置jvm大小,因为eclipse运行时自己也需要jvm,所以eclipse.ini里设置的jvm大小不是具体某个程序运行时所用jvm的大小,这和具体程序运行的jvm大小无关。

那么怎么才能设置某个程序的jvm大小呢(当然控制台运行的话不会存在这个问题,如:java-Xms256m-Xmx1024mclassname,这样就可以把当前程序的jvm大小给设定)?

因为Eclipse里默认的一个程序的jvm配置为:-Xms8m-Xmx128m,所以我们的处理耗内存比较大时需要手动调整一下,以便不会内存溢出。具体的设置方法为:

     选中被运行的类,点击菜单‘run->run...’,选择(x)=Argument标签页下的vm arguments框里输入 -Xmx512m, 保存运行。

                                                                                                                                                                                   

相关知识:Hot Spot JVM5中的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调优

为了能够将JVMGC的调优能够使用在具体的实践当中,下面将利用若干个例子来说明GC的调优.

例1:Heapsize设置

JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heapsize的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn-Xms-Xmx等选项可进行设置。Heapsize的大小是YoungGeneration和TenuredGeneraion之和。

当在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-Xmx32mSwingSet2.jar

      提示:Heap Size 最大不要超过可用物理内存的80%,一般的要将-Xms和-Xmx选项设置为相同,而-Xmn为1/4的-Xmx值。

例2:Young Generation(-Xmn)的设置

在本例中看一下YoungGeneration的设置不同将有什么现象发生。

假设将Younggeneration的大小设置为4M,即执行java-jar-verbose:gc-Xmn4m-Xms32m-Xmx32m-XX:+PrintGCDetailsSwingSet2.jar,屏幕输出如下(节选)

[GC[DefNew:3968K->64K(4032K),0.0923407secs]3968K->2025K(32704K),0.0931870secs]

[GC[DefNew:4021K->64K(4032K),0.0356847secs]5983K->2347K(32704K),0.0365441secs]

[GC[DefNew:3995K->39K(4032K),0.0090603secs]6279K->2372K(32704K),0.0093377secs]

[GC[DefNew:3992K->23K(4032K),0.0057540secs]6325K->2356K(32704K),0.0060290secs]

[GC[DefNew:3984K->27K(4032K),0.0013058secs]6317K->2360K(32704K),0.0015888secs]

[GC[DefNew:3981K->59K(4032K),0.0023307secs]6315K->2422K(32704K),0.0026091secs]

将程序体制并将YoungGeneration的大小设置为8M,即执行java-jar-verbose:gc-Xmn8m-Xms32m-Xmx32m-XX:+PrintGCDetailsSwingSet2.jar,屏幕输出如下(节选)

[GC[DefNew:7808K->192K(8000K),0.1016784secs]7808K->2357K(32576K),0.1022834secs]

[GC[DefNew:8000K->70K(8000K),0.0149659secs]10165K->2413K(32576K),0.0152557secs]

[GC[DefNew:7853K->59K(8000K),0.0069122secs]10196K->2403K(32576K),0.0071843secs]

[GC[DefNew:7867K->171K(8000K),0.0075745secs]10211K->2681K(32576K),0.0078376secs]

[GC[DefNew:7970K->192K(8000K),0.0201353secs]10480K->2923K(32576K),0.0206867secs]

[GC[DefNew:7979K->30K(8000K),0.1787079secs]10735K->4824K(32576K),0.1790065secs]

那么根据GC输出的信息(这里取第一行)做一下Minor收集的比较。可以看出两次的Minor收集分别在Younggeneration中找回3904K(3968K->64K)和7616K(7808K->192K)而对于整个jvm则找回1943K(3968K->2025)和5451K(7808K->2357K)。第一种情况下Minor收集了大约50%(1943/3904)的对象,而另外的50%的对象则被移到了tenuredgeneration。在第二中情况下Minor收集了大约72%的对象,只有不到30%的对象被移到了TenuredGeneration.这个例子说明此应用在的Younggeneration设置为4m时显的偏小。

     提示:一般的Young Generation的大小是整个Heap size的1/4。Young generation的minor收集率应一般在70%以上。当然在实际的应用中需要根据具体情况进行调整。

例3:Young Generation对应用响应的影响

还是使用-Xmn4m和-Xmn8m进行比较,先执行下面的命令

    java -jar -verbose:gc -Xmn4m -Xms32m -Xmx32m-XX:+Print GC Details -XX:+Print GC ApplicationConcurrentTime-XX:+Print GC ApplicationStoppedTime SwingSet2.jar

屏幕输出如下(节选)

Applicationtime:0.5114944seconds

[GC[DefNew:3968K->64K(4032K),0.0823952secs]3968K->2023K(32704K),0.0827626secs]

Totaltimeforwhichapplicationthreadswerestopped:0.0839428seconds

Applicationtime:0.9871271seconds

[GC[DefNew:4020K->64K(4032K),0.0412448secs]5979K->2374K(32704K),0.0415248secs]

Totaltimeforwhichapplicationthreadswerestopped:0.0464380seconds

YoungGeneration的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.1293306 secs]

122.463:[GC122.463:[DefNew:8128K->8128K(8128K),0.0000560secs]122.463:[Tenured:18630K->2366K(24576K),0.1322560secs]26758K->2366K(32704K),0.1325284secs]

133.896:[GC133.897:[DefNew:8128K->8128K(8128K),0.0000443secs]133.897:[Tenured:18240K->2573K(24576K),0.1340199secs]26368K->2573K(32704K),0.1343218secs]

144.112:[GC144.112:[DefNew:8128K->8128K(8128K),0.0000544secs]144.112:[Tenured:16564K->2304K(24576K),0.1246831secs]24692K->2304K(32704K),0.1249602secs]

再执行java-verbose:gc-Xmn8m-Xmx64m-XX:+PririntGCDetails-XX:+PrintGCTimeStampsjava类,命令行将提示(只提取了Major收集)

90.597:[GC90.597:[DefNew:8128K->8128K(8128K),0.0000542secs]90.597:[Tenured:49841K->5141K(57344K),0.2129882secs]57969K->5141K(65472K),0.2133274secs]

120.899:[GC120.899:[DefNew:8128K->8128K(8128K),0.0000550secs]120.899:[Tenured:50384K->2430K(57344K),0.2216590secs]58512K->2430K(65472K),0.2219384secs]

153.968:[GC153.968:[DefNew:8128K->8128K(8128K),0.0000511secs]153.968:[Tenured:51164K->2309K(57344K),0.2193906secs]59292K->2309K(65472K),0.2196372secs]

可以看出在Heapsize为32m的时候系统等候时间约为0.13秒左右,而设置为64m的时候等候时间则增大到0.22秒左右了。但是在32m的时候系统的Major收集间隔为10秒左右,而Heapsize增加到64m的时候为30秒。那么应用在运行的时候是选择32m还是64m呢?如果应用是web类型(即要求有大的吞吐量)的应用则使用64m(即heapsize大一些)的比较好。对于要求实时响应要求较高的场合(例如GUI型的应用)则使用32m比较好一些。

注意:

1。因为在JVM5运行时已经对Heap-size进行了优化,所以在能确定java应用运行时不会超过默认的Heapsize的情况下建议不要对这些值进行修改。

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-Xmx32mSwingSet2.jar

系统将输出如下信息(片段〕

[GC7807K->2641K(32576K),0.0676654secs]

[GC10436K->3108K(32576K),0.0245328secs]

[GC10913K->3176K(32576K),0.0072865secs]

[GC10905K->4097K(32576K),0.0223928secs]

之后再执行java-jar-server-verbose:gc-XX:+UseParNewGC-Xmn8m-Xms32m-Xmx32mSwingSet2.jar

系统将输出如下信息(片段〕

[ParNew7808K->2656K(32576K),0.0447687secs]

[ParNew10441K->3143K(32576K),0.0179422secs]

[ParNew10951K->3177K(32576K),0.0031914secs]

[ParNew10985K->3867K(32576K),0.0154991secs]

很显然使用了-XX:+UseParNewGC选项的minor收集的时间要比不使用的时候优。

例6:如何缩短major收集的时间

下面比较一下采用-XX:+UseConcMarkSweepGC选项和不采用它的时候的major收集将有什么不同。先执行

java-jar-verbose:gc-XX:+UseConcMarkSweepGC-XX:+UseParNewGC-Xmn64m-Xms256m-Xmx256mSwingSet2.jar

系统将输出如下信息(片段〕

[FullGC22972K->18690K(262080K),0.2326676secs]

[FullGC18690K->18690K(262080K),0.1701866secs

之后再执行java-jar-verbose:gc-XX:+UseParNewGC-Xmn64m-Xms256m-Xmx256mSwingSet2.jar

系统将输出如下信息(片段〕

[FullGC56048K->18869K(260224K),0.3104852secs]

提示:此选项在Heap Size 比较大而且Major收集时间较长的情况下使用更合适。

例7:关于-server选项在JVM中将运行中的类认定为server-class的时候使用此选项。SUN 的Hot Spot JVM5如果判断到系统的配置满足如下条件则自动将运行的类认定为server-class,并且会自动设置jvm的选项(当没有手工设置这选项的时候〕而且HOTSPOTJVM5提供了自动调优的功能,他会根据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。YoungGeneration:用于存放“早逝”对象(即瞬时对象)。例如:在创建对象时或者调用方法时使用的临时对象或局部变量。

2。TenuredGeneration:用于存放“驻留”对象(即较长时间被引用的对象)。往往体现为一个大型程序中的全局对象或长时间被使用的对象。

3。Perm Generation:用于存放“永久”对象。这些对象管理着运行于JVM中的类和方法。

JVM选项的分类

JVM有这么几种选项供使用.

1.供-X选项使用的项目,又称为非标准选项,不同厂商的此类型选项是有所不同的。例如:IBM的JVM用的一些选项在Sun的JVM中就不一定能生效。这种选项的使用方式如下:

java-Xmn16m-Xms64m-Xmx64mjava类名

2.供-XX选项使用的项目,这种类型的选项可能要求有对系统信息访问的权限。所以要慎用。这种选项的使用方式如下:

java-XX:MaxHeapFreeRatio=70-XX:+PrintGCDetailsjava类名

3.java选项(即在命令行执行java后提示的选项).

java -server -verbose:gc -d64 java类名

垃圾收集分类

在JVM中有两种垃圾方式,一种叫做Minor(次收集),另一种叫做Major(主收集)。其中Minor在Young Generation的空间被对象全部占用后执行,主要是对YoungGeneration中的对象进行垃圾收集。而Major是针对于整个Heapsize的垃圾收集。其中Minor方式的收集经常发生,并且Minor收集所占用的系统时间小。Major方式的垃圾收集则是一种“昂贵”的垃圾收集方式,因为在Major要对整个Heap size进行垃圾收集,这会使得应用停顿的时间变得较长。

GC信息的格式

[GC [<collector>: <starting occupancy1>-> <ending occupancy1>, <pause time1> secs] <startingoccupancy3> -> <ending occupancy3>, <pause time3>secs]

<collector>GC为minor收集过程中使用的垃圾收集器起的内部名称.

<startingoccupancy1>younggeneration在进行垃圾收集前被对象使用的存储空间.

<endingoccupancy1>younggeneration在进行垃圾收集后被对象使用的存储空间

<pausetime1>minor收集使应用暂停的时间长短(秒)

<startingoccupancy3>整个堆(HeapSize)在进行垃圾收集前被对象使用的存储空间

<endingoccupancy3>整个堆(HeapSize)在进行垃圾收集后被对象使用的存储空间

<pausetime3>整个垃圾收集使应用暂停的时间长短(秒),包括major收集使应用暂停的时间(如果发生了major收集).

GC信息的选项

-XX:+PrintGCDetails显示GC的详细信息

-XX:+PrintGCApplicationConcurrentTime打印应用执行的时间

-XX:+PrintGCApplicationStoppedTime打印应用被暂停的时间

提示:1.":"后的"+"号表示开启此选项,如果是"-"号那么表示关闭此选项。

     2.在不同的选项和不同的收集方式和类型下输出的格式会有所不同。

                                                                                                                                                                              

警告:

关于Java堆的管理—垃圾回收编程准则:

(1)不要试图去假定垃圾收集发生的时间,这一切都是未知的。比如,方法中的一个临时对象在方法调用完毕后就变成了无用对象,这个时候它的内存就可以被释放。

(2)Java中提供了一些和垃圾收集打交道的类,而且提供了一种强行执行垃圾收集的方法--调用System.gc(),但这同样是个不确定的方法。Java 中并不保证每次调用该方法就一定能够启动垃圾收集,它只不过会向JVM发出这样一个申请,到底是否真正执行垃圾收集,一切都是个未知数。

(3)挑选适合自己的垃圾收集器。一般来说,如果系统没有特殊和苛刻的性能要求,可以采用JVM的缺省选项。否则可以考虑使用有针对性的垃圾收集器,比如增量收集器就比较适合实时性要求较高的系统之中。系统具有较高的配置,有比较多的闲置资源,可以考虑使用并行标记/清除收集器。

(4)关键的也是难把握的问题是内存泄漏。良好的编程习惯和严谨的编程态度永远是最重要的,不要让自己的一个小错误导致内存出现大漏洞。

(5)尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为null,暗示垃圾收集器来收集该对象,还必须注意该引用的对象是否被监听,如果有,则要去掉监听器,然后再赋空值。

       就是说,对于频繁申请内存和释放内存的操作,还是自己控制一下比较好,但是System.gc()的方法不一定适用,最好使用finallize强制执行或者写自己的finallize方法。

相关推荐