一些参数
-XX:TargetSurvivorRatio=N
该参数表示survivor区的使用率,hot spot默认是50,如果survivor区的对象大小超过了使用率则会以survivor区age的最大值作为晋升老年代的参考,最终取该参考值与设置的最大晋升代数的较小值作为晋升代数。默认50可以确保在一次young gc过后,survivor区至少有一半的空间用于接收从eden区过来的对象。如果设置成100则几乎可以确保对象只有当age达到最大晋升代数才会进入老年代,或者当survivor区无法容纳一次young gc后的对象,那么部分对象将进入老年代。在项目中使用过100的参数,情景是jvm会持续不断的大量分配对象,并且伴随大对象的分配,jvm进行young gc和full gc频率非常高。我们的目标是尽量使得对象不进入老年代减小full gc频率,因此这里使用了100来充分的利用survivor区,使得短暂存活的大对象相对较少的进入老年代,更多的在新生代被回收。这样使用的弊端个人认为两方面,一是容纳来自eden区对象的能力变差,在某些情况下导致对象大量进入老年代,伴随full gc风险,二是survivor区使用率提高将会对young gc的停顿时间有影响。-XX:UseAdaptiveSizePolicy与-XX:+UseParNewGC
前者表示是否使用自适应策略调整堆内存大小,默认是打开的,如果我们想固定新生代老年代的大小,需要关闭该标志。后者不多介绍了吧,和CMS默契配合的新生代并行收集器,当启用该收集器的时候,前者是默认关闭的。-XX:CMSParallelRemarkEnabled
这个参数表示是否在CMS的标记阶段使用并发标记-XX:ParallelGCThreads=n和-XX:ConcGCThreads
前者表示进行垃圾收集时的并行线程数目,可以理解为这些线程运行与stw阶段,后者表示垃圾收集时的并发线程数目,非stw阶段,典型的就是cms和g1。
对于前者,jvm推荐根据cpu核数N进行计算,如果N<8,那么jvm使用N个线程,如果大于8则每增加一核则增加5/8个线程
ParallelGCThreads= 8 + (N - 8) * 5 / 8
我们可以理解为尽量不要让并行线程数大于CPU核数,太多的并行线程数会让CPU忙于调度反而增大GC时间。
对于后者,默认情况下根据ParallelGCThreads计算,有
ConcGCThreads = (3 + ParallelGCThreads) / 4
这个值的设置涉及到应用程序的停顿以及并发时间的时间,显然线程数更多,垃圾时间速度更快,同样过多垃圾线程数也会造成CPU忙于调度,而另一个问题则是由于并发垃圾收集应用程序也在运行,因此应用程序线程会和并发线程争抢CPU,在垃圾收集线程数过多的情况下可能会影响应用程序的响应。-XX:UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction
前者表示是否在CMS发生Full gc之后是否进行空间整理,默认开启;
后者表示进行多少次full gc之后进行一次空间整理,默认值为零,即每次full gc过后都进行空间整理,空间整理是stw的,因此可以根据碎片的严重情况设置后者的取值,如果碎片不严重可以将值设置得大一些,不过实际web应用中貌似都使用默认值,换言之两者都可以不需要设置。-XX:UseTLAB
使用线程堆空间,该区域属于eden区,默认开启,大小有 eden空间大小、线程数以及线程分配率共同动态决定。
使用该值会为每个线程分配一定量空间分配对象,这样有利于减少多个线程并发的获得空间产生的性能损失。通过-XX:+PrintTLAB可以打印tlab的使用日志。通过日志如果我们发现大部分对象都不使用tlab进行分配则有必要增大tlab的值或者干脆不使用tlab,使用-XX:TLABSize=N可以设置初始值,-XX:-ResizeTLAB则可以禁止动态分配tlab的大小。
当tlab无法分配为一个对象分配足够空间时,jvm会选择直接在对上分配还是回收当前的tlab在重新分配一个新的tlab来完成对象分配,做这个决定依赖于refill_waste,超过该值会在堆上分配,该值默认是为tlab的1%,也可以使用-XX:TLABWasteTargetPercent=N进行设置,每当发生一次堆上分配,jvm会根据-XX:TLABWasteIncrement=N(默认4)进行增量设置。这个值的增大也会增加tlab被重新分配的可能性,因此refill_waste的调整效果并不那么确定。-XX:StringTableSize=N
字符串保留表(使用hashtable)的大小,该表存在于永久代,字符串保留表可以在存在大量重复字符串对象的时候只保留一份从而节省空间大小,通过-XX:StringTableSize可以设置保留表的桶数目小,默认值是1009或者60013,java7u40之前为前者,如果自行设置最好是素数,有更好的散列效果。向字符串保留表动态添加的方法是使用intern(),使用-XX:PrintStringTableStatistics可以显示字符串保留表的使用情况。理论上表越大,冲突越小更利于快速的查找,我们可以根据对存储快照在mat中查看字符串的使用情况来决定是否需要更大的字符串保留表。-XX:UseCompressedOops
该参数在jdk6u25之后已经默认开启,即压缩指针(包括对象头里面的类指针,对象指针和数组指针)。压缩指针只有在64位操作系统才有效,我们知道64位操作系统的指针大小是64位的,如果jvm内部的对象指针也采用这个大小就浪费了,原因是我们不大可能为jvm分配高达2^64 字节的内存。为了节省空间,出现了压缩指针,即采用32位的jvm指针来指向java对象,这里一定注意jvm指针不同于系统内存指针,jvm指针指向的是java对象的起始,而系统内存指针指向的是系统内存某一个内存位置,压缩的jvm指针和jvm启动时的内存基址做移位操作得到真实的内存地址。那么32的jvm指针最大可访问的内存空间是多少呢?答案是2^32 *8。原因在于java默认使用8字节的内存对齐,换句话说jvm指针指向的不同对象的起始地址之间的间隔应该是8字节的整数倍。通过-XX:ObjectAlignmentInBytes=16可以设置16字节的对象对齐,这样开启压缩指针的jvm理论上可以使用64g内存,弊端是16字节对齐会带来更多的内存浪费。
说了这么多最重要的是压缩指针可以有效的降低jvm的内存开销,如果jvm的堆空间不超过32g,建议开启该参数。关于该参数的更多底层细节可以参考R大这篇文章:
http://rednaxelafx.iteye.com/blog/1010079
关于对象头信息以及一个对象究竟占据多少字节数可以参考这篇文章
http://www.importnew.com/19172.html
说说G1
G1不严格区分新生代老年代的区域,它将整个堆分成多个大小相等的区域,有点类似于操作系统的分页,不同的是G1要求对象必须是在连续的区域中不能散列到不连续的区域。G1的垃圾收集分为如下的阶段(G1的gc也不是严格意义上的full gc)
- 初始标记(STW),该阶段标记哪些区域有root引用,即标记根区域,这个阶段附加在了一次young gc里面,这样增加了一次young gc的时间但是减少了STW的总时间
- 标记根区域,该阶段扫描出根区域中哪些引用指向老年代对象,过程中如果年轻带需要执行young gc需要等待该阶段结束
- 并发标记,查找堆中所有存活的对象,该阶段可被young gc中断
- 再次标记(STW),再次标记堆中存活的对象
- 清理阶段(STW, NOT STW)清理阶段第一部分是STW的,这部分会统计存活对象以及空闲区域,并且擦写RSets(rememberSets),非STW阶段则释放完全空闲的区域
- 复制(STW),将存活的对象进行整理移动到其他区域以压缩空间(根据官方文档的说明,该阶段可以是young gc也可以时mix gc)
以上前面五个步骤完成了G1的并发GC过程,这个过程仅仅只是标记了哪些区域具有更大的回收价值,g1将会选择具有更大回收价值的区域进行回收,这也是G1名字的由来gabage first,同时由于分区的思想可以实现不完整的标记整理算法以压缩空间,因此相对于CMS,G1的碎片化问题不那么严重。同时g1将垃圾收集阶段进行了更细致的切割,STW的部分如果可以放在young gc就和young gc一起进行,降低STW总时间。
真正进行老年代垃圾回收的过程被附加在了young gc之中,即所谓的mix gc(混合式GC),mix gc会几乎持续到老年代垃圾完全被收集,使用几乎的原因在于在收集过程中可能下一次的g1 gc已经开始,这里mix gc执行的次数可以由参数-XX:G1MixedGCCountTarget=N设置。
g1的应用场景: - full gc频繁并且耗时过长
- 垃圾回收和整理时间过长
- 对象分配的频率以及晋升代数变化频繁
- 堆大小最好超过6G
g1最佳实践 - 不要设置年轻代大小,一旦设置g1将不能动态调节堆大小来达到指定的停顿时间目标
- 设置指定的停顿时间目标 -XX:MaxGCPauseMillis=<N>
- 针对晋升失败的问题,第一减小-XX:G1MixedGCCountTarget=N的值(快速完成mix gc),第二是增大-XX:G1ReservePercent=n(保留内存)的值(预留更多内存避免晋升失败)
- CPU资源充足可以增加垃圾收集线程数
-
减小-XX:InitiatingHeapOccupancyPercent=n的值提前启动垃圾收集
G1参数
G1的问题
- 并发模式失效,即G1 GC过程中老年代被填满,解决办法还是三板斧,增加堆大小,提前启动GC,适当增加gc线程数
- 晋升失败,指mix gc阶段老年代被填满了,解决方案上面已经提及
- 疏散失败,young gc过程中新生代老年代都无法容纳对象
- 分配大对象失败,这是g1的一个问题,虽然碎片化问题已经较好解决,但是由于对象必须占据连续的内存空间,因此还是存在大对象无法找到连续的区域而产生full gc的问题(莫名其妙的full gc缔造者)
- 其他g1参数,使用参数需要配合-XX:+UnlockExperimentalVMOptions
-XX:G1MixedGCLiveThresholdPercent 表示mix gc时,老年代中存活对象的比例不能超过该值,默认65%(实验参数)
-XX:G1HeapWastePercent 允许被浪费的百分比,如果垃圾比例小于该门限,即便堆使用率达到门限也不会进行垃圾收集,默认10%
-XX:G1OldCSetRegionThresholdPercent 一次mix gc期间可被回收的最大的老年代的比例上限,默认10%(实验参数)
-XX:G1ReservePercent 保留空间,虚拟机至少保证晋升时有这么多的空间可以使用,默认10%
参考:http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html#ScanningRememberedSets
http://blog.csdn.net/renfufei/article/details/41897113
RSETS参考:http://www.xue163.com/2262/1/22624099.html