JVM 内存溢出(转载~)

对于JVM的内存写过的文章已经有点多了,并且有点烂了,不过说那么多大多数在解决OOM的状况,于此,本文就只阐述这个内容,携带一些分析和理解和部分扩展内容,也就是JVM宕机中的一些问题,OK,下面说下OOM的常见状况:java

 

第一类内存溢出,也是你们认为最多,第一反应认为是的内存溢出,就是堆栈溢出:web

那什么样的状况就是堆栈溢出呢?当你看到下面的关键字的时候它就是堆栈溢出了:算法

java.lang.OutOfMemoryError: ......java heap space.....windows

也就是当你看到heap相关的时候就确定是堆栈溢出了,此时若是代码没有问题的状况下,适当调整-Xmx和-Xms是能够避免的,不过必定是代码没有问题的前提,为何会溢出呢,要么代码有问题,要么访问量太多而且每一个访问的时间太长或者数据太多,致使数据释放不掉,由于垃圾回收器是要找到那些是垃圾才能回收,这里它不会认为这些东西是垃圾,天然不会去回收了;主意这个溢出以前,可能系统会提早先报错关键字为:服务器

java.lang.OutOfMemoryError:GC over head limit exceeded多线程

这种状况是当系统处于高频的GC状态,并且回收的效果依然不佳的状况,就会开始报这个错误,这种状况通常是产生了不少不能够被释放的对象,有多是引用使用不当致使,或申请大对象致使,可是java heap space的内存溢出有可能提早不会报这个错误,也就是可能内存就直接不够致使,而不是高频GC.架构

 

第二类内存溢出,PermGen的溢出,或者PermGen 满了的提示,你会看到这样的关键字:并发

关键信息为:框架

java.lang.OutOfMemoryError: PermGen spacejvm

缘由:系统的代码很是多或引用的第三方包很是多、或代码中使用了大量的常量、或经过intern注入常量、或者经过动态代码加载等方法,致使常量池的膨胀,虽然JDK 1.5之后能够经过设置对永久带进行回收,可是咱们但愿的是这个地方是不作GC的,它够用就行,因此通常状况下今年少作相似的操做,因此在面对这种状况经常使用的手段是:增长-XX:PermSize和-XX:MaxPermSize的大小。

 

第三类内存溢出:在使用ByteBuffer中的allocateDirect()的时候会用到,不少javaNIO的框架中被封装为其余的方法

溢出关键字:

java.lang.OutOfMemoryError: Direct buffer memory
若是你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不作clear的时候就会出现相似的问题,常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,若是常规的应用程序你要将一个文件的内容输出到客户端须要经过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),而后再输出到直接内存由操做系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存能够直接由应用程序本身控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存,因此要注意了哦。

若是常常有相似的操做,能够考虑设置参数:-XX:MaxDirectMemorySize

 

第四类内存溢出错误:

溢出关键字:

java.lang.StackOverflowError

这个参数直接说明一个内容,就是-Xss过小了,咱们申请不少局部调用的栈针等内容是存放在用户当前所持有的线程中的,线程在jdk 1.4之前默认是256K,1.5之后是1M,若是报这个错,只能说明-Xss设置得过小,固然有些厂商的JVM不是这个参数,本文仅仅针对Hotspot VM而已;不过在有必要的状况下能够对系统作一些优化,使得-Xss的值是可用的。

 

第五类内存溢出错误:

溢出关键字:

java.lang.OutOfMemoryError: unable to create new native thread

上面第四种溢出错误,已经说明了线程的内存空间,其实线程基本只占用heap之外的内存区域,也就是这个错误说明除了heap之外的区域,没法为线程分配一块内存区域了,这个要么是内存自己就不够,要么heap的空间设置得太大了,致使了剩余的内存已经很少了,而因为线程自己要占用内存,因此就不够用了,说明了缘由,如何去修改,不用我多说,你懂的。

 

第六类内存溢出:

溢出关键字

java.lang.OutOfMemoryError: request {} byte for {}out of swap

这类错误通常是因为地址空间不够而致使。

 

六大类常见溢出已经说明JVM中99%的溢出状况,要逃出这些溢出状况很是困难,除非一些很怪异的故障问题会发生,好比因为物理内存的硬件问题,致使了code cache的错误(在由byte code转换为native code的过程当中出现,可是几率极低),这种状况内存 会被直接crash掉,相似还有swap的频繁交互在部分系统中会致使系统直接被crash掉,OS地址空间不够的话,系统根本没法启动,呵呵;JNI的滥用也会致使一些本地内存没法释放的问题,因此尽可能避开JNI;socket链接数据打开过多的socket也会报相似:IOException: Too many open files等错误信息。

 

JNI就不用多说了,尽可能少用,除非你的代码太牛B了,我无话可说,呵呵,这种内存若是没有在被调用的语言内部将内存释放掉(如C语言),那么在进程结束前这些内存永远释放不掉,解决办法只有一个就是将进程kill掉。

 

另外GC自己是须要内存空间的,由于在运算和中间数据转换过程当中都须要有内存,因此你要保证GC的时候有足够的内存哦,若是没有的话GC的过程将会很是的缓慢。

 

顺便这里就说起一些新的CMS GC的内容和策略(有点乱,每次写都很乱,可是能看多少看多少吧):

首先我再写一次一前博客中的已经写过的内容,就是不少参数没啥建议值,建议值是本身在现场根据实际状况科学计算和测试获得的综合效果,建议值没有绝对好的,并且默认值不少也是有问题的,由于不一样的版本和厂商都有很大的区别,默认值没有永久都是同样的,就像-Xss参数的变化同样,要看到你当前的java程序heap的大体状况能够这样看看(如下参数是随便设置的,并非什么默认值):

$sudo jmap -heap `pgrep java` 
Attaching to process ID 4280, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 19.1-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 134217728 (128.0MB)
MaxNewSize = 134217728 (128.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 134217728 (128.0MB)
MaxPermSize = 268435456 (256.0MB)

Heap Usage:
PS Young Generation
Eden Space:
capacity = 85721088 (81.75MB)
used = 22481312 (21.439849853515625MB)
free = 63239776 (60.310150146484375MB)
26.22611602876529% used
From Space:
capacity = 24051712 (22.9375MB)
used = 478488 (0.45632171630859375MB)
free = 23573224 (22.481178283691406MB)
1.9894134770946867% used
To Space:
capacity = 24248320 (23.125MB)
used = 0 (0.0MB)
free = 24248320 (23.125MB)
0.0% used
PS Old Generation
capacity = 939524096 (896.0MB)
used = 16343864 (15.586723327636719MB)
free = 923180232 (880.4132766723633MB)
1.7395896571023124% used
PS Perm Generation
capacity = 134217728 (128.0MB)
used = 48021344 (45.796722412109375MB)
free = 86196384 (82.20327758789062MB)
35.77868938446045% used

 

付:sudo是须要拿到管理员权限,若是你的系统权限很大那么就不须要了,最后的grep java那个内容若是不对,能够直接经过jps或者ps命令将和java相关的进程号直接写进去,如:java -map 4280,这个参数其实彻底能够经过jstat工具来替代,并且看到的效果更加好,这个参数在线上应用中,尽可能少用(尤为是高并发的应用中),可能会触发JVM的bug,致使应用挂起;在jvm 1.6u14后能够编写任意一段程序,而后在运行程序的时候,增长参数为:-XX:+PrintFlagsFinal来输出当前JVM中运行时的参数值,或者经过jinfo来查看,jinfo是很是强大的工具,能够对部分参数进行动态修改,固然内存相关的东西是不能修改的,只能增长一些不是很相关的参数,有关JVM的工具使用,后续文章中若是有机会咱们再来探讨,不是本文的重点;补充:关于参数的默认值对不一样的JVM版本、不一样的厂商、运行于不一样的环境(通常和位数有关系)默认值会有区别。

 

OK,再说下反复的一句,没有必要的话就不要乱设置参数,参数不是拿来玩的,默认的参数对于这门JDK都是有好处的,关键是否适合你的应用场景,通常来说你常规的只须要设置如下几个参数就能够了:

-server 表示为服务器端,会提供不少服务器端默认的配置,如并行回收,而服务器上通常这个参数都是默认的,因此都是能够省掉,与之对应的还有一个-client参数,通常在64位机器上,JVM是默认启动-server参数,也就是默认启动并行GC的,可是是ParallelGC而不是ParallelOldGC,二者算法不一样(后面会简单说明下),而比较特殊的是windows 32位上默认是-client,这两个的区别不只仅是默认的参数不同,在jdk包下的jre包下通常会包含client和server包,下面分别对应启动的动态连接库,而真正看到的java、javac等相关命令指示一个启动导向,它只是根据命令找到对应的JVM并传入jvm中进行启动,也就是看到的java.exe这些文件并非jvm;说了这么多,最终总结一下就是,-server和-client就是彻底不一样的两套VM,一个用于桌面应用,一个用于服务器的。

-Xmx 为Heap区域的最大值

-Xms 为Heap区域的初始值,线上环境须要与-Xmx设置为一致,不然capacity的值会来回飘动,飘得你心旷神怡,你懂的。

-Xss(或-ss) 这个其实也是能够默认的,若是你真的以为有设置的必要,你就改下吧,1.5之后是1M的默认大小(指一个线程的native空间),若是代码很少,能够设置小点来让系统能够接受更大的内存。注意,还有一个参数是-XX:ThreadStackSize,这两个参数在设置的过程当中若是都设置是有冲突的,通常按照JVM常理来讲,谁设置在后面,就以谁为主,可是最后发现若是是在1.6以上的版本,-Xss设置在后面的确都是以-Xss为主,可是要是-XX:ThreadStackSize设置在后面,主线程仍是为-Xss为主,而其它线程以-XX:ThreadStackSize为主,主线程作了一个特殊断定处理;单独设置都是以自己为主,-Xss不设置也不会采用其默认值,除非两个都不设置会采用-Xss的默认值。另外这个参数针对于hotspot的vm,在IBM的jvm中,还有一个参数为-Xoss,主要缘由是IBM在对栈的处理上有操做数栈和方法栈等各类不一样的栈种类,而hotspot无论是什么栈都放在一个私有的线程内部的,不区分是什么栈,因此只须要设置一个参数,而IBM的J9不是这样的;有关栈上的细节,后续咱们有机会专门写文章来讲明。

 

-XX:PermSize与-XX:MaxPermSize两个包含了class的装载的位置,或者说是方法区(但不是本地方法区),在Hotspot默认状况下为64M,主意全世界的JVM只有hostpot的VM才有Perm的区域,或者说只有hotspot才有对用户能够设置的这块区域,其余的JVM都没有,其实并非没有这块区域,而是这块区域没有让用户来设置,其实这块区域自己也不该该让用户来设置,咱们也没有一个明确的说法这块空间必需要设置多大,都是拍脑壳设置一个数字,若是发布到线上看下若是用得比较多,就再多点,若是用的少,就减小点,而这块区域和性能关键没有多大关系,只要能装下就OK,而且时不时会由于Perm不够而致使Full GC,因此交给开发者来调节这个参数不知道是怎么想的;因此Oracle将在新一代JVM中将这个区域完全删掉,也就是对用户透明,G1的若是真正稳定起来,之后JVM的启动参数将会很是简单,并且理论上管理再大的内存也是没有问题的,其实G1(garbage first,一种基于region的垃圾收集回收器)已经在hotspot中开始有所试用,不过目前效果很差,还不如CMS呢,因此只是试用,G1已经做为ORACLE对JVM研发的最高重点,CMS自如今最高版本后也再也不有新功能(能够修改bug),该项目已经进行5年,还没有发布正式版,CMS是四五年前发布的正式版,可是是最近一两年才开始稳定,而G1的复杂性将会远远超越CMS,因此要真正使用上G1还有待考察,全世界目前只有IBM J9真正实现了G1论文中提到的思想(论文于05年左右发表),IBM已经将J9应用于websphere中,可是并不表明这是全世界最好的jvm,全世界最好的jvm是Azul(无停顿垃圾回收算法和一个零开销的诊断/监控工具),几乎能够说这个jvm是没有暂停的,在全世界不少顶尖级的公司使用,不过价格很是贵,不能直接使用,目前这个jvm的主导者在研究JRockit,而目前hotspot和JRockit都是Oracle的,因此他们可能会合并,因此咱们应该对JVM的性能充满信心。

 

也就是说你经常使用的状况下只须要设置4个参数就OK了,除非你的应用有些特殊,不然不要乱改,那么来看看一些其余状况的参数吧:

 

先来看个不大经常使用的,就是你们都知道JVM新的对象应该说几乎百分百的在Eden里面,除非Eden真的装不下,咱们不考虑这种变态的问题,由于线上环境Eden区域都是不小的,来下降GC的次数以及全局 GC的几率;而JVM习惯将内存按照较为连续的位置进行分配,这样使得有足够的内存能够被分配,减小碎片,那么对于内存最后一个位置必然就有大量的征用问题,JVM在高一点的版本里面提出了为每一个线程分配一些私有的区域来作来解决这个问题,而1.5后的版本还能够动态管理这些区域,那么如何本身设置和查看这些区域呢,看下英文全称为:Thread Local Allocation Buffer,简称就是:TLAB,即内存本地的持有的buffer,设置参数有:

-XX:+UseTLAB 启用这种机制的意思
-XX:TLABSize=<size in kb> 设置大小,也就是本地线程中的私有区域大小(只有这个区域放不下才会到Eden中去申请)。
-XX:+ResizeTLAB 是否启动动态修改

这几个参数在多CPU下很是有用。

-XX:+PrintTLAB 能够输出TLAB的内容。

 

下面再闲扯些其它的参数:

 

若是你须要对Yong区域进行并行回收应该如何修改呢?在jdk1.5之后可使用参数:

-XX:+UseParNewGC

注意: 与它冲突的参数是:-XX:+UseParallelOldGC和-XX:+UseSerialGC,若是须要用这个参数,又想让整个区域是并行回收的,那么就使用-XX:+UseConcMarkSweepGC参数来配合,其实这个参数在使用了CMS后,默认就会启动该参数,也就是这个参数在CMS GC下是无需设置的,后面会说起到这些参数。

 

 

默认服务器上的对Full并行GC策略为(这个时候Yong空间回收的时候启动PSYong算法,也是并行回收的):

-XX:+UseParallelGC

另外,在jdk1.5后出现一个新的参数以下,这个对Yong的回收算法和上面同样,对Old区域会有所区别,上面对Old回收的过程当中会作一个全局的Compact,也就是全局的压缩操做,而下面的算法是局部压缩,为何要局部压缩呢?是由于JVM发现每次压缩后再逻辑上数据都在Old区域的左边位置,申请的时候从左向右申请,那么生命力越长的对象就通常是靠左的,因此它认为左边的对象就是生命力很强,并且较为密集的,因此它针对这种状况进行部分密集,可是这两种算法mark阶段都是会暂停的,并且存活的对象越多活着的越多;而ParallelOldGC会进行部分压缩算法(主意一点,最原始的copy算法是不须要通过mark阶段,由于只须要找到一个或活着的就只须要作拷贝就能够,而Yong区域借用了Copy算法,只是惟一的区别就是传统的copy算法是采用两个相同大小的内存来拷贝,浪费空间为50%,因此分代的目标就是想要实现不少优点所在,认为新生代85%以上的对象都应该是死掉的,因此S0和S1通常并非很大),该算法为jdk 1.5之后对于绝大部分应用的最佳选择。

-XX:+UseParallelOldGC

 

-XX:ParallelGCThread=12:并行回收的线程数,最好根据实际状况而定,由于线程多每每存在征用调度和上下文切换的开销;并且也并不是CPU越多线程数也能够设置越大,通常设置为12就再增长用处也不大,主要是算法自己内部的征用会致使其线程的极限就是这样。

 

设置Yong区域大小:

-Xmn Yong区域的初始值和最大值同样大

-XX:NewSize和-XX:MaxNewSize若是设置觉得同样大就是和-Xmn,在JRockit中会动态变化这些参数,根据实际状况有可能会变化出两个Yong区域,或者没有Yong区域,有些时候会生出来一个半长命对象区域;这里除了这几个参数外,还有一个参数是NewRatio是设置Old/Yong的倍数的,这几个参数都是有冲突的,服务器端建议是设置-Xmn就能够了,若是几个参数所有都有设置,-Xmn和-XX:NewSize与-XX:MaxNewSize将是谁设置在后面,以谁的为准,而-XX:NewSize -XX:MaxNewSize与-XX:NewRatio时,那么参数设置的结果可能会如下这样的(jdk 1.4.1后):

min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))

-XX:NewRatio为Old区域为Yong的多少倍,间接设置Yong的大小,1.6中若是使用此参数,则默认会在适当时候被动态调整,具体请看下面参数UseAdaptiveSizepollcy 的说明。

三个参数不要同时设置,由于都是设置Yong的大小的。

 

-XX:SurvivorRatio:该参数为Eden与两个求助空间之一的比例,注意Yong的大小等价于Eden + S0 + S1,S0和S1的大小是等价的,这个参数为Eden与其中一个S区域的大小比例,如参数为8,那么Eden就占用Yong的80%,而S0和S1分别占用10%。

之前的老版本有一个参数为:-XX:InitialSurivivorRatio,若是不作任何设置,就会以这个参数为准,这个参数的默认值就是8,不过这个参数并非Eden/Survivor的大小,而是Yong/Survivor,因此因此默认值8,表明每个S区域的空间大小为Yong区域的12.5%而不是10%。另外顺便说起一下,每次你们看到GC日志的时候,GC日志中的每一个区域的最大值,其中Yong的空间最大值,始终比设置的Yong空间的大小要小一点,大概是小12.5%左右,那是由于每次可用空间为Eden加上一个Survivor区域的大小,而不是整个Yong的大小,由于可用空间每次最可能是这样大,两个Survivor区域始终有一块是空的,因此不会加上两个来计算。

 

-XX:MaxTenuringThreshold=15:在正常状况下,新申请的对象在Yong区域发生多少次GC后就会被移动到Old(非正常就是S0或S1放不下或者不太可能出现的Eden都放不下的对象),这个参数通常不会超过16(由于计数器从0开始计数,因此设置为15的时候至关于生命周期为16)。

要查看如今的这个值的具体状况,可使用参数:-XX:+PrintTenuringDistribution

 

经过上面的jmap应该能够看出个人机器上的MinHeapFreeRatio和MaxHeapFreeRatio分别为40个70,也就是你们常常说的在GC后剩余空间小于40%时capacity开始增大,而大于70%时减少,因为咱们不但愿让它移动,因此这两个参数几乎没有意义,若是你须要设置就设置参数为:

-XX:MinHeapFreeRatio=40
-XX:MaxHeapFreeRatio=70

 

JDK 1.6后有一个动态调节板块的,固然若是你的每个板块都是设置固定值,这个参数也没有用,不过若是是非固定的,建议仍是不要动态调整,默认是开启的,建议将其关掉,参数为:

-XX:+UseAdaptiveSizepollcy 建议使用-XX:-UseAdaptiveSizepollcy关掉,为何当你的参数设置了NewRatio、Survivor、MaxTenuringThreshold这几个参数若是在启动了动态更新状况下,是无效的,固然若是你设置-Xmn是有效的,可是若是设置的比例的话,初始化可能会按照你的参数去运行,不过运行过程当中会经过必定的算法动态修改,监控中你可能会发现这些参数会发生改变,甚至于S0和S1的大小不同。

若是启动了这个参数,又想要跟踪变化,那么就使用参数:-XX:+PrintAdaptiveSizePolicy

 

上面已经提到,javaNIO中经过Direct内存来提升性能,这个区域的大小默认是64M,在适当的场景能够设置大一些。

-XX:MaxDirectMemorySize

 

一个不太经常使用的参数:

-XX:+ScavengeBeforeFullGC 默认是开启状态,在full GC前先进行minor GC。

 

对于java堆中若是要设置大页内存,能够经过设置参数:

付:此参数必须在操做系统的内核支持的基础上,须要在OS级别作操做为:

echo 1024 > /proc/sys/vm/nr_hugepages

echo 2147483647 > /proc/sys/kernel/shmmax

-XX:+UseLargePages

-XX:LargePageSizeInBytes

此时整个JVM都将在这块内存中,不然所有不在这块内存中。

 

javaIO的临时目录设置

-Djava.io.tmpdir

jstack会去寻找/tmp/hsperfdata_admin下去寻找与进程号相同的文件,32位机器上是没有问题的,64为机器的是有BUG的,在jdk 1.6u23版本中已经修复了这个bug,若是你遇到这个问题,就须要升级JDK了。

 

还记得上次说的平均晋升大小吗,在并行GC时,若是平均晋升大小大于old剩余空间,则发生full GC,那么当小于剩余空间时,也就是平均晋升小于剩余空间,可是剩余空间小于eden + 一个survivor的空间时,此时就依赖于参数:

-XX:-HandlePromotionFailure

启动该参数时,上述状况成立就发生minor gc(YGC),大于则发生full gc(major gc)。

 

通常默认直接分配的对象若是大于Eden的一半就会直接晋升到old区域,可是也能够经过参数来指定:

-XX:PretenureSizeThreshold=2m 我我的不建议使用这个参数

也就是当申请对象大于这个值就会晋升到old区域。

 

传说中GC时间的限制,一个是经过比例限制,一个是经过最大暂停时间限制,可是GC时间能限制么,呵呵,在增量中貌似能够限制,不过不能限制住GC整体的时间,因此这个参数也不是那么关键。

-XX:GCTimeRatio=

-XX:MaxGCPauseMillis

-XX:GCTimeLimit

要看到真正暂停的时间就一个是看GCDetail的日志,另外一个是设置参数看:

-XX:+PrintGCApplicationStoppedTime

 

有些人,有些人就是喜欢在代码里面里头写System.gc(),耍酷,这个不是测试程序是线上业务,这样将会致使N多的问题,很少说了,你应该懂的,不懂的话看下书吧,而RMI是很不听话的一个鸟玩意,EJB的框架也是基于RMI写的,RMI为何不听话呢,就是它本身在里面非要搞个System.gc(),哎,为了放置频繁的作,频繁的作,你就将这个命令的执行禁用掉吧,固然程序不用改,否则那些EJB都跑步起来了,呵呵:

-XX:+DisableExplicitGC 默认是没有禁用掉,写成+就是禁用掉的了,可是有些时候在使用allocateDirect的时候,不少时候还真须要System.gc来强制回收这块资源。

 

内存溢出时导出溢出的错误信息:
-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/home/xieyu/logs/ 这个参数指定导出时的路径,否则导出的路径就是虚拟机的目标位置,很差找了,默认的文件名是:java_pid<进程号>.hprof,这个文件能够相似使用jmap -dump:file=....,format=b <pid>来dump相似的内容,文件后缀都是hprof,而后下载mat工具进行分析便可(不过内存有多大dump文件就多大,而本地分析的时候内存也须要那么大,因此不少时候下载到本地都没法启动是很正常的),后续文章有机会咱们来讲明这些工具,另外jmap -dump参数也不要常常用,会致使应用挂起哦;另外此参数只会在第一次输出OOM的时候才会进行堆的dump操做(java heap的溢出是能够继续运行再运行的程序的,至于web应用是否服务要看应用服务器自身如何处理,而c heap区域的溢出就根本没有dump的机会,由于直接就宕机了,目前系统没法看到c heap的大小以及内部变化,要看大小只能间接经过看JVM进程的内存大小(top或相似参数),这个大小通常会大于heap+perm的大小,多余的部分基本就能够认为是c heap的大小了,而看内部变化呢只有google perftools能够达到这个目的),若是内存过大这个dump操做将会很是长,因此hotspot若是之后想管理大内存,这块必须有新的办法出来。

最后,用dump出来的文件,经过mat分析出来的结果每每有些时候难以直接肯定到底哪里有问题,能够看到的维度大概有:那个类使用的内存最多,以及每个线程使用的内存,以及线程内部每个调用的类和方法所使用的内存,可是不少时候没法断定究竟是程序什么地方调用了这个类或者方法,由于这里只能看到最终消耗内存的类,可是不知道谁使用了它,一个办法是扫描代码,可是太笨重,并且若是是jar包中调用了就很差弄了,另外一种方法是写agent,那么就须要相应的配合了,可是有一个很是好的工具就是btrace工具(jdk 1.7貌似还不支持),能够跟踪到某个类的某个方法被那些类中的方法调用过,那这个问题就好说了,只要知道开销内存的是哪个类,就能知道谁调用过它,OK,关于btrace的不是本文重点,网上都有,后续文章有机会再探讨,
原理:
No performance impact during runtime(无性能影响)
Dumping a –Xmx512m heap
Create a 512MB .hprof file(512M内存就dump出512M的空间大小)
JVM is “dead” during dumping(死掉时dump)
Restarting JVM during this dump will cause unusable .hprof file(重启致使文件不可用)

 

注明的NUMA架构,在JVM中开始支持,固然也须要CPU和OS的支持才能够,须要设置参数为:

-XX:+UseNUMA 必须在并行GC的基础上才有的

老年代没法分配区域的最大等待时间为(默认值为0,可是也不要去动它):

-XX:GCExpandToAllocateDelayMillis

让JVM中全部的set和get方法转换为本地代码:

-XX:+UseFastAccessorMethods

以时间戳输出Heap的利用率

-XX:+PrintHeapUsageOverTime

在64bit的OS上面(其实通常达不到57位左右),因为指针会放大为8个byte,因此会致使空间使用增长,固然,若是内存够大,就没有问题,可是若是升级到64bit系统后,只是想让内存达到4G或者8G,那么就彻底能够经过不少指针压缩为4byte就OK了,因此在提供如下参数(本参数于jdk 1.6u23后使用,并自动开启,因此也不须要你设置,知道就OK):

-XX:+UseCompressedOops 请注意:这个参数默认在64bit的环境下默认启动,可是若是JVM的内存达到32G后,这个参数就会默认为不启动,由于32G内存后,压缩就没有多大必要了,要管理那么大的内存指针也须要很大的宽度了。

后台JIT编译优化启动

-XX:+BackgroundCompilation

若是你要输出GC的日志以及时间戳,相关的参数有:

-XX:+PrintGCDetails 输出GC的日志详情,包含了时间戳

-XX:+PrintGCTimeStamps 输出GC的时间戳信息,按照启动JVM后相对时间的每次GC的相对秒值(毫秒在小数点后面),也就是每次GC相对启动JVM启动了多少秒后发生了此次GC

-XX:+PrintGCDateStamps输出GC的时间信息,会按照系统格式的日期输出每次GC的时间

-XX:+PrintGCTaskTimeStamps输出任务的时间戳信息,这个细节上比较复杂,后续有文章来探讨。

-XX:-TraceClassLoading 跟踪类的装载

-XX:-TraceClassUnloading 跟踪类的卸载

-XX:+PrintHeapAtGC 输出GC后各个堆板块的大小。

将常量信息GC信息输出到日志文件:

-Xloggc:/home/xieyu/logs/gc.log

 

 

如今面对大内存比较流行是是CMS GC(最少1.5才支持),首先明白CMS的全称是什么,不是传统意义上的内容管理系统(Content Management System)哈,第一次我也没看懂,它的全称是:Concurrent Mark Sweep,三个单词分别表明并发、标记、清扫(主意这里没有compact操做,其实CMS GC的确没有compact操做),也就是在程序运行的同时进行标记和清扫工做,至于它的原理前面有说起过,只是有不一样的厂商在上面作了一些特殊的优化,好比一些厂商在标记根节点的过程当中,标记完当前的根,那么这个根下面的内容就不会被暂停恢复运行了,而移动过程当中,经过读屏障来看这个内存是否是发生移动,若是在移动稍微停一下,移动过去后再使用,hotspot还没这么厉害,暂停时间仍是挺长的,只是相对其余的GC策略在面对大内存来说是不错的选择。

 

下面看一些CMS的策略(并发GC总时间会比常规的并行GC长,由于它是在运行时去作GC,不少资源征用都会影响其GC的效率,而整体的暂停时间会短暂不少不少,其并行线程数默认为:(上面设置的并行线程数 + 3)/ 4

 

付:CMS是目前Hotspot管理大内存最好的JVM,若是是常规的JVM,最佳选择为ParallelOldGC,若是必需要以响应时间为准,则选择CMS,不过CMS有两个隐藏的隐患:

一、CMS GC虽然是并发且并行运行的GC,可是初始化的时候若是采用默认值92%(JVM 1.5的白皮书上描述为68%实际上是错误的,1.6是正确的),就很容易出现问题,由于CMS GC仅仅针对Old区域,Yong区域使用ParNew算法,也就是Old的CMS回收和Yong的回收能够同时进行,也就是回收过程当中Yong有可能会晋升对象Old,而且业务也能够同时运行,因此92%基本开始启动CMS GC颇有可能old的内存就不够用了,当内存不够用的时候,就启动Full GC,而且这个Full GC是串行的,因此若是弄的很差,CMS会比并行GC更加慢,为何要启用串行是由于CMS GC、并行GC、串行GC的继承关系决定的,简单说就是它没办法去调用并行GC的代码,细节说后续有文章来细节说明),建议这个值设置为70%左右吧,不过具体时间仍是本身决定。

二、CMS GC另外一个大的隐患,其实不看也差很少应该清楚,看名字就知道,就是不会作Compact操做,它最恶心的地方也在这里,因此上面才说通常的应用都不使用它,它只有内存垃圾很是多,多得没法分配晋升的空间的时候才会出现一次compact,可是这个是Full GC,也就是上面的串行,很恐怖的,因此内存不是很大的,不要考虑使用它,并且它的算法十分复杂。

 

还有一些小的隐患是:和应用一块儿征用CPU(不过这个不是大问题,增长CPU便可)、整个运行过程当中时间比并行GC长(这个也不是大问题,由于咱们更加关心暂停时间而不是运行时间,由于暂停会影响很是多的业务)。

启动CMS为全局GC方法(注意这个参数也不能上面的并行GC进行混淆,Yong默认是并行的,上面已经说过

-XX:+UseConcMarkSweepGC

在并发GC下启动增量模式,只能在CMS GC下这个参数才有效。

-XX:+CMSIncrementalMode

启动自动调节duty cycle,即在CMS GC中发生的时间比率设置,也就是说这段时间内最大容许发生多长时间的GC工做是能够调整的。

-XX:+CMSIncrementalPacing

在上面这个参数设定后能够分别设置如下两个参数(参数设置的比率,范围为0-100):

-XX:CMSIncrementalDutyCycleMin=0
-XX:CMSIncrementalDutyCycle=10

增量GC上还有一个保护因子(CMSIncrementalSafetyFactor),不太经常使用;CMSIncrementalOffset提供增量GC连续时间比率的设置;CMSExpAvgFactor为增量并发的GC增长权重计算。

-XX:CMSIncrementalSafetyFactor=
-XX:CMSIncrementalOffset= 
-XX:CMSExpAvgFactor=

 

是否启动并行CMS GC(默认也是开启的)

-XX:+CMSParallelRemarkEnabled

要单独对CMS GC设置并行线程数就设置(默认也不须要设置):

-XX:ParallelCMSThreads

 

对PernGen进行垃圾回收:

JDK 1.5在CMS GC基础上须要设置参数(也就是前提是CMS GC才有):

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

1.6之后的版本无需设置:-XX:+CMSPermGenSweepingEnabled,注意,其实一直以来Full GC都会触发对Perm的回收过程,CMS GC须要有一些特殊照顾,虽然VM会对这块区域回收,可是Perm回收的条件几乎不太可能实现,首先须要这个类的classloader必须死掉,才能够将该classloader下全部的class干掉,也就是要么所有死掉,要么所有活着;另外,这个classloader下的class没有任何object在使用,这个也太苛刻了吧,由于常规的对象申请都是经过系统默认的,应用服务器也有本身默认的classloader,要让它死掉可能性不大,若是这都死掉了,系统也应该快挂了。

 

CMS GC由于是在程序运行时进行GC,不会暂停,因此不能等到不够用的时候才去开启GC,官方说法是他们的默认值是68%,可是惋惜的是文档写错了,通过不少测试和源码验证这个参数应该是在92%的时候被启动,虽然还有8%的空间,可是仍是很可怜了,当CMS发现内存实在不够的时候又回到常规的并行GC,因此不少人在没有设置这个参数的时候发现CMS GC并无神马优点嘛,和并行GC一个鸟样子甚至于更加慢,因此这个时候须要设置参数(这个参数在上面已经说过,启动CMS必定要设置这个参数):

-XX:CMSInitiatingOccupancyFraction=70

这样保证Old的内存在使用到70%的时候,就开始启动CMS了;若是你真的想看看默认值,那么就使用参数:-XX:+PrintCMSInitiationStatistics 这个变量只有JDK 1.6可使用 1.5不能够,查看实际值-XX:+PrintCMSStatistics;另外,还能够设置参数-XX:CMSInitiatingPermOccupancyFraction来设置Perm空间达到多少时启动CMS GC,不过意义不大。

JDK 1.6之后有些时候启动CMS GC是根据计算代价进行启动,也就是不必定按照你指定的参数来设置的,若是你不想让它按照所谓的成原本计算GC的话,那么你就使用一个参数:-XX:+UseCMSInitiatingOccupancyOnly,默认是false,它就只会按照你设置的比率来启动CMS GC了。若是你的程序中有System.gc以及设置了ExplicitGCInvokesConcurrent在jdk 1.6中,这种状况使用NIO是有可能产生问题的。

 

启动CMS GC的compation操做,也就是发生多少次后作一次全局的compaction:

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction:发生多少次CMS Full GC,这个参数最好不要设置,由于要作compaction的话,也就是真正的Full GC是串行的,很是慢,让它本身去决定何时须要作compaction。

 

-XX:CMSMaxAbortablePrecleanTime=5000 设置preclean步骤的超时时间,单位为毫秒,preclean为cms gc其中一个步骤,关于cms gc步骤比较多,本文就不细节探讨了。

 

并行GC在mark阶段,可能会同时发生minor GC,old区域也可能发生改变,因而并发GC会对发生了改变的内容进行remark操做,这个触发的条件是:

-XX:CMSScheduleRemarkEdenSizeThreshold

-XX:CMSScheduleRemarkEdenPenetration

即Eden区域多大的时候开始触发,和eden使用量超过百分比多少的时候触发,前者默认是2M,后者默认是50%。

可是若是长期不作remark致使old作不了,能够设置超时,这个超时默认是5秒,能够经过参数:

-XX:CMSMaxAbortablePrecleanTime

-XX:+ExplicitGCInvokesConcurrent 在显示发生GC的时候,容许进行并行GC。

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 几乎和上面同样,只不过多一个对Perm区域的回收而已。

 

补充:

其实JVM还有不少的版本,不少的厂商,与其优化的原则,随便举两个例子hotspot在GC中作的一些优化(这里不说代码的编译时优化或运行时优化):

Eden申请的空间对象由Old区域的某个对象的一个属性指向(也就是Old区域的这个空间不回收,Eden这块就没有必要考虑回收),因此Hotspot在CPU写上面,作了一个屏障,当发生赋值语句的时候(对内存来说赋值就是一种写操做),若是发现是一个新的对象由Old指向Eden,那么就会将这个对象记录在一个卡片机里面,这个卡片机是有不少512字节的卡片组成,当在YGC过程当中,就基本不会去移动或者管理这块对象(付:这种卡片机会在CMS GC的算法中使用,不过和这个卡片不是放在同一个地方的,也是CMS GC的关键,对于CMS GC的算法细节描述,后续文章咱们单独说明)。

Old区域对于一些比较大的对象,JVM就不会去管理个对象,也就是compact过程当中不会去移动这块对象的区域等等吧。

 

以上大部分参数为hotspot的自带关于性能的参数,参考版本为JDK 1.5和1.6的版本,不少为我的经验说明,不足以说明全部问题,若是有问题,欢迎探讨;另外,JDK的参数是否是就只有这些呢,确定并非,我知道的也不止这些,可是有些以为不必说出来的参数和一些数学运算的参数我就不想给出来了,好比像禁用掉GC的参数有神马意义,咱们的服务器要是把这个禁用掉干个屁啊,呵呵,作测试还能够用这玩玩,让它不作GC直接溢出;还有一些什么计算因子啥的,还有不少复杂的数学运算规则,要是把这个配置明白了,就太那个了,并且通常状况下也没那个必要,JDK到如今的配置参数多达上500个以上,要知道完的话慢慢看吧,不过意义不大,并且要知道默认值最靠谱的是看源码而不是看文档,官方文档也只能保证绝大部是正确的,不能保证全部的是正确的。

相关文章
相关标签/搜索