一、 常用参数
-Xms
JVM启动时申请的初始Heap值,默认为操作系统物理内存的1/64但小于1G。默认当空余堆内存大于70%时,JVM会减小heap的大小到-Xms指定的大小,可通过-XX:MaxHeapFreeRation=来指定这个比列。Server端JVM最好将-Xms和-Xmx设为相同值,避免每次垃圾回收完成后JVM重新分配内存;开发测试机JVM可以保留默认值。(例如:-Xms4g)
-Xmx
JVM可申请的最大Heap值,默认值为物理内存的1/4但小于1G,默认当空余堆内存小于40%时,JVM会增大Heap到-Xmx指定的大小,可通过-XX:MinHeapFreeRation=来指定这个比列。最佳设值应该视物理内存大小及计算机内其他内存开销而定。(例如:-Xmx4g)
-Xmn
Java Heap Young区大小。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小(相对于HotSpot 类型的虚拟机来说)。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。(例如:-Xmn2g)
程序新创建的对象都是从年轻代分配内存,年轻代由Eden Space和两块相同大小的SurvivorSpace(通常又称S0和S1或From和To)构成,可通过-Xmn参数来指定年轻代的大小,也可以通过-XX:SurvivorRation来调整Eden Space及SurvivorSpace的大小。
老年代用于存放经过多次新生代GC仍然存活的对象,例如缓存对象,新建的对象也有可能直接进入老年代,主要有两种情况:1、大对象,可通过启动参数设置-XX:PretenureSizeThreshold=1024(单位为字节,默认为0)来代表超过多大时就不在新生代分配,而是直接在老年代分配。2、大的数组对象,且数组中无引用外部对象。老年代所占的内存大小为-Xmx对应的值减去-Xmn对应的值。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
-Xss
Java每个线程的Stack大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。(例如:-Xss1024K)
-XX:PermSize
持久代(方法区)的初始内存大小。(例如:-XX:PermSize=64m)
-XX:MaxPermSize
持久代(方法区)的最大内存大小。(例如:-XX:MaxPermSize=512m)
-XX:+UseSerialGC
串行(SerialGC)是jvm的默认GC方式,一般适用于小型应用和单处理器,算法比较简单,GC效率也较高,但可能会给应用带来停顿。
-XX:+UseParallelGC
并行(ParallelGC)是指多个线程并行执行GC,一般适用于多处理器系统中,可以提高GC的效率,但算法复杂,系统消耗较大。(配合使用:-XX:ParallelGCThreads=8,并行收集器的线程数,此值最好配置与处理器数目相等)
-XX:+UseParNewGC
设置年轻代为并行收集,JKD5.0以上,JVM会根据系统配置自行设置,所以无需设置此值。
-XX:+UseParallelOldGC
设置年老代为并行收集,JKD6.0出现的参数选项。
-XX:+UseConcMarkSweepGC
并发(ConcMarkSweepGC)是指GC运行时,对应用程序运行几乎没有影响(也会造成停顿,不过很小而已),GC和app两者的线程在并发执行,这样可以最大限度不影响app的运行。
-XX:+UseCMSCompactAtFullCollection
在Full GC的时候,对老年代进行压缩整理。因为CMS是不会移动内存的,因此非常容易产生内存碎片。因此增加这个参数就可以在FullGC后对内存进行压缩整理,消除内存碎片。当然这个操作也有一定缺点,就是会增加CPU开销与GC时间,所以可以通过-XX:CMSFullGCsBeforeCompaction=3 这个参数来控制多少次Full GC以后进行一次碎片整理。
-XX:+CMSInitiatingOccupancyFraction=80
代表老年代使用空间达到80%后,就进行Full GC。CMS收集器在进行垃圾收集时,和应用程序一起工作,所以,不能等到老年代几乎完全被填满了再进行收集,这样会影响并发的应用线程的空间使用,从而再次触发不必要的Full GC。
-XX:+MaxTenuringThreshold=10
垃圾的最大年龄,代表对象在Survivor区经过10次复制以后才进入老年代。如果设置为0,则年轻代对象不经过Survivor区,直接进入老年代。
二、 分类
JVM启动参数共分为三类:
1、标准参数(-),所有的JVM实现都必须实现这些参数的功能,而且向后兼容。例如:-verbose:class(输出jvm载入类的相关信息,当jvm报告说找不到类或者类冲突时可此进行诊断);-verbose:gc(输出每次GC的相关情况);-verbose:jni(输出native方法调用的相关情况,一般用于诊断jni调用错误信息)。
2、非标准参数(-X),默认jvm实现这些参数的功能,但是并不保证所有jvm实现都满足,且不保证向后兼容。例如:-Xms512m;-Xmx512m;-Xmn200m;-Xss128k;-Xloggc:file(与-verbose:gc功能类似,只是将每次GC事件的相关情况记录到一个文件中,文件的位置最好在本地,以避免网络的潜在问题。若与verbose命令同时出现在命令行中,则以-Xloggc为准)。
3、非Stable参数(-XX),此类参数各个jvm实现会有所不同,将来可能会随时取消,需要慎重使用。例如:-XX:PermSize=64m;-XX:MaxPermSize=512m。
三、 引用
主要介绍JVM内存参数的详细配置,与GC策略(原:http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html)。
不管是YGC还是Full GC,GC过程中都会对导致程序运行中中断,正确的选择不同的GC策略,调整JVM、GC的参数,可以极大的减少由于GC工作,而导致的程序运行中断方面的问题,进而适当的提高Java程序的工作效率。但是调整GC是以个极为复杂的过程,由于各个程序具备不同的特点,如:web和GUI程序就有很大区别(Web可以适当的停顿,但GUI停顿是客户无法接受的),而且由于跑在各个机器上的配置不同(主要cup个数,内存不同),所以使用的GC种类也会不同(如何选择见GC种类及如何选择)。本文将注重介绍JVM、GC的一些重要参数的设置来提高系统的性能。 JVM内存组成及GC相关内容请见之前的文章:JVM内存组成 GC策略&内存申请。JVM参数的含义实例见实例分析如下:
并行收集器相关参数:
CMS相关参数:
辅助信息:
3.1 GC性能方面的考虑
对于GC的性能主要有2个方面的指标:吞吐量throughput(工作时间不算gc的时间占总的时间比)和暂停pause(gc发生时app对外显示的无法响应)。
1、Total Heap,默认情况下,vm会增加/减少heap大小以维持free space在整个vm中占的比例,这个比例由MinHeapFreeRatio和MaxHeapFreeRatio指定。一般而言,server端的app会有以下规则:对vm分配尽可能多的memory;将Xms和Xmx设为一样的值。如果虚拟机启动时设置使用的内存比较小,这个时候又需要初始化很多对象,虚拟机就必须重复地增加内存。处理器核数增加,内存也跟着增大。
2、The Young Generation,另外一个对于app流畅性运行影响的因素是young generation的大小。younggeneration越大,minor collection越少;但是在固定heap size情况下,更大的young generation就意味着小的tenured generation,就意味着更多的major collection(major collection会引发minor collection)。NewRatio反映的是young和tenured generation的大小比例。NewSize和MaxNewSize反映的是young generation大小的下限和上限,将这两个值设为一样就固定了young generation的大小(同Xms和Xmx设为一样)。如果希望,SurvivorRatio也可以优化survivor的大小,不过这对于性能的影响不是很大。SurvivorRatio是eden和survior大小比例。一般而言,server端的app会有以下规则:首先决定能分配给vm的最大的heap size,然后设定最佳的young generation的大小;如果heap size固定后,增加young generation的大小意味着减小tenured generation大小。让tenured generation在任何时候够大,能够容纳所有live的data(留10%-20%的空余)。
3.2 经验&规则
1、年轻代大小选择:响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,年轻代收集发生的频率也是最小的.同时,减少到达年老代的对象;吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用;避免设置过小.当新生代设置过小时会导致:1.YGC次数更加频繁 2.可能导致YGC对象直接进入旧生代,如果此时旧生代满了,会触发FGC.
2、年老代大小选择:响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得,并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在年轻代和年老代回收上的时间比例。吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
3、较小堆引起的碎片问题:因为年老代的并发收集器使用标记,清除算法,所以不会对堆进行压缩.当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象.但是,当堆空间较小时,运行一段时间以后,就会出现"碎片",如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记,清除方式进行回收.如果出现"碎片",可能需要进行如下配置:-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩;-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩。
4、使用CMS的好处是用尽量少的新生代,经验值是128M-256M, 然后老生代利用CMS并行收集, 这样能保证系统低延迟的吞吐效率。 实际上cms的收集停顿时间非常的短,2G的内存, 大约20-80ms的应用程序停顿时间
5、系统停顿的时候可能是GC的问题也可能是程序的问题,多用jmap和jstack查看,或者killall -3 java,然后查看java控制台日志,能看出很多问题。(相关工具的使用方法将在后面的blog中介绍)。
6、仔细了解自己的应用,如果用了缓存,那么年老代应该大一些,缓存的HashMap不应该无限制长,建议采用LRU算法的Map做缓存,LRUMap的最大长度也要根据实际情况设定。
7、采用并发回收时,年轻代小一点,年老代要大,因为年老大用的是并发回收,即使时间长点也不会影响其他程序继续运行,网站不会停顿。
8、JVM参数的设置(特别是 –Xmx –Xms –Xmn-XX:SurvivorRatio -XX:MaxTenuringThreshold等参数的设置没有一个固定的公式,需要根据PV old区实际数据 YGC次数等多方面来衡量。为了避免promotion faild可能会导致xmn设置偏小,也意味着YGC的次数会增多,处理并发访问的能力下降等问题。每个参数的调整都需要经过详细的性能测试,才能找到特定应用的最佳配置。
3.3 PromotionFailed:
垃圾回收时promotionfailed是个很头痛的问题,一般可能是两种原因产生,第一个原因是救助空间不够,救助空间里的对象还不应该被移动到年老代,但年轻代又有很多对象需要放入救助空间;第二个原因是年老代没有足够的空间接纳来自年轻代的对象;这两种情况都会转向Full GC,网站停顿时间较长。
解决方方案一:
第一个原因我的最终解决办法是去掉救助空间,设置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二个原因我的解决办法是设置CMSInitiatingOccupancyFraction为某个值(假设70),这样年老代空间到70%时就开始执行CMS,年老代有足够的空间接纳来自年轻代的对象。
解决方案一的改进方案:
又有改进了,上面方法不太好,因为没有用到救助空间,所以年老代容易满,CMS执行会比较频繁。我改善了一下,还是用救助空间,但是把救助空间加大,这样也不会有promotion failed。具体操作上,32位Linux和64位Linux好像不一样,64位系统似乎只要配置MaxTenuringThreshold参数,CMS还是有暂停。为了解决暂停问题和promotion failed问题,最后我设置-XX:SurvivorRatio=1 ,并把MaxTenuringThreshold去掉,这样即没有暂停又不会有promotoin failed,而且更重要的是,年老代和永久代上升非常慢(因为好多对象到不了年老代就被回收了),所以CMS执行频率非常低,好几个小时才执行一次,这样,服务器都不用重启了。
-Xmx4000M -Xms4000M -Xmn600M-XX:PermSize=500M -XX:MaxPermSize=500M -Xss256K -XX:+DisableExplicitGC-XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC-XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0-XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M-XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly-XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0-XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps-XX:+PrintHeapAtGC -Xloggc:log/gc.log
3.4 CMSInitiatingOccupancyFraction值与Xmn的关系公式
上面介绍了promontionfaild产生的原因是EDEN空间不足的情况下将EDEN与From survivor中的存活对象存入To survivor区时,To survivor区的空间不足,再次晋升到old gen区,而old gen区内存也不够的情况下产生了promontion faild从而导致full gc.那可以推断出:eden+from survivor < old gen区剩余内存时,不会出现promontion faild的情况,即:
(Xmx-Xmn)*(1-CMSInitiatingOccupancyFraction/100)>=(Xmn-Xmn/(SurvivorRatior+2)) 进而推断出:CMSInitiatingOccupancyFraction<=((Xmx-Xmn)-(Xmn-Xmn/(SurvivorRatior+2)))/(Xmx-Xmn)*100
例如:当xmx=128 xmn=36SurvivorRatior=1时CMSInitiatingOccupancyFraction<=((128.0-36)-(36-36/(1+2)))/(128-36)*100=73.913;
当xmx=128 xmn=24SurvivorRatior=1时CMSInitiatingOccupancyFraction<=((128.0-24)-(24-24/(1+2)))/(128-24)*100=84.615…
当xmx=3000 xmn=600SurvivorRatior=1时 CMSInitiatingOccupancyFraction<=((3000.0-600)-(600-600/(1+2)))/(3000-600)*100=83.33
CMSInitiatingOccupancyFraction低于70% 需要调整xmn或SurvivorRatior值。
一、什么是GC?
二、什么时候发生GC?
宏观上看:GC发生的时间在整个Java程序的生命周期上是不可预估的,是由系统自身决定,或者程序调用System.GC()。
微观上看:
(分代回收) 现在的虚拟机大多采用分代收集算法,所谓的“分代收集”就是 根据对象的存活周期不同,将内存划分为几块,一般将Java堆分为 新生代 和 老年代 ,并根据各个年代的特点采用 不同的、最优的 回收策略,通常有minor gc 和 full gc。
minor gc: 使用的是复制算法,针对新生代进行GC。
- 由于Java中的大部分对象通常都是招生夕灭的,新生代是GC垃圾回收最为频繁的一个区域,新生代分为 Eden区和两个Survivor区,其内存大小比例为8:1:1,绝大数新产生的对象会存储在新生代的Eden,大对象会直接放在老年代。
- 每当Eden产生对象时,都会检查Eden区是否可以将这个对象存下,可以存下则存,如果不可以的话就会触发minor gc。 将Eden区和Survivor from区根据复制算法把所有的存活对象复制到 Survivor to区,随后回收Eden区和Survivor区,回收后再存储对象。
- 但如果是极端情况下,经过minor gc后还是无法放下这个对象,JVM就根据空间分配担保机制,如果允许担保就将Survivor无法容纳的对象直接进入老年代。这个时候如果老年代还是分配不下的话触发一次full GC,如果还是不可以的话就会抛出OOM异常。
- 还有就是在新生代每经一次 minor gc 对象的年龄就加一,当对象的年龄大于一定年龄,就会将这个对象移入老年代(默认的是15岁,也可以通过参数设置),但并不是永远要等到对象年龄大于指定年龄才把对象放入老年代,JVM会有个 动态对象年龄判定 机制,如果在 survivor 空间中相同年龄的所有对象的总和大于 Survivor 空间的一半时,就将年龄大于等于该相同年龄的对象直接进入老年代。
- 而且对于比较大的对象则直接分配到老年代。
如果minorGC后,JVM就根据空间分配担保机制,如果 不允许担保 不允许将Survivor直接进入老年代,会怎么样???
full gc: 发生在老年代,使用的是标记算法。
- 老年代的对象一般由新生代转移过来,或者大对象的直接分配。
- 在升到老年代的对象 大于 老年代剩余空间时发生full gc,对老年代进行清理,清理后再将新生代的对象升入老年代,如果这时老年代空闲空间还是不足以容纳升入老年代的对象时,就会触发OOM异常。
- 或者小于时,由于 空间分配担保机制 强制full gc。在minor gc 之前 检查到minor gc 不安全,而且虚拟机不允许垃圾回收器冒险进行minor gc ,这是就会触发 full gc,尽量在老年代腾出足够大的空间,以防minor gc 出现风险。
- 如果已经为了担保而发生full gc过后,还是无法腾出足够大的空间进行担保,那么只好再发起一次 full gc。但是为了避免频繁 full gc, 大多数情况下还是将 HandlePromotionFailure 参数打开,允许GC 进行冒险地进行minor gc。
- full gc 的过程就是 扫描出存活对象,然后进行回收未标记的对象,回收后对空出的空间进行合并,要么标记出来用于下次进行分配,总之就是要减少内存碎片带来的效率的损耗,如果发生过full gc 后还是无法存放对象,则将抛出OOM异常。
垃圾回收算法
- 标记-清楚法:首先它先标记出所有需要回收的对象,标记完成后统一将所有标记的对象进行回收。优缺点:(1) 一个是清除后会产生大量不连续的空间,将内存碎片化,如果要存储一个大的对象,可能虽然有很多未使用的空间,但没有那么大的连续空间,还是无法存储,发生OOM;(2) 第二个是其标记和清除的 效率很低 ,GC时发生GC停顿,再使用低效率的标记清楚法对程序的影响还是很大的。
- 复制算法:将内存划分为两个等大的空间,每次将存活的对象整齐复制到另一空间,并将已使用的空间全部清理。优缺点:内存分配就没有内存的碎片化问题,但是需要浪费一半的内存空间。
- 标记-整理法:和标记-清除法类似,但是不是对可回收对象进行清理,而是将存活的对象统一移动的内存的一端,然后清理到边界外的内存即可。优缺点:较标记清除法不会出现内存的碎片化,但是随之带来的是每次GC都要多次执行复制操作,效率会很低。
注: 一般垃圾收集器会使用分代回收,新生代(Eden/Survivor1/Survivor2)采用 复制算法,老年代使用标记(Mark)算法,扫描出存活对象,然后进行回收未标记的对象,回收后对空出的空间进行合并,要么标记出来用于下次进行分配,总之就是要减少内存碎片带来的效率的损耗。
空间分配担保机制
- 新生代分配对象在eden区,当存储不下时会触发Minor gc ,清理Eden区和Survivor from去,并把存活对象转义到Survivor to区,但是经过Minor GC后,Survivor to区还是不足以存下存活的对象,这时候就会将存储不下的对象担保金老年代进行存放,但是如果老年代也存储不下,就会存在Minor GC 的风险。
- 空间分配担保就是 在Minor GC 之前检查 是否存在这种风险,如果不存在则正常Minor GC ,如果存在 则根据 HandlePromotionFailure 的设置,询问JVM允不允许冒险的进行 Minor GC,如果允许则冒险进行 Minor GC,如果不允许冒险,则会将老年代进行 full gc ,在老年代腾出足够大的空间保证Minor GC 是安全的。
- 但是如果为了Minor GC的安全性进行了Full GC,Full GC过后还是无法保证安全性,就会再次出发Full GC,直至保证Minor Gc安全才停止,这样会导致 Full GC的频繁进行,使系统的效率会很低,在一般情况下会打开HandlePromotionFailure 参数,允许风险的Minor gc进行,防止full gc 频繁进行。
- 那么如何判断Minor gc是否存在风险呢?因为风险就是系统不确定如果Minor gc 后新生代还未分配的对象是否能都在老年代存储,这时只需要比较 老年代最大可用的连续空间 和 新生代所有对象的总空间即可,如果老年代最大连续空间还是较大的,说明即使新生代分配不下,在老年代还是可以将新生代的所有对象转移到老年代为新生对象腾出空间。
如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
也就是,将HandlePromotionFailure 设置为true,则不会出现重复的进行fgc的情况,出问题了就直接 oom了。
三、对什么东西进行GC?
GC对的主要是系统不再需要的对象,而JVM可通过两种算法判断对象是否使用,一种是 引用计数法 ,一种是 可达性分析法。
引用计数法: 会给每个对象添加一个计数器,每当被引用,计数器就加一,当失去引用时,计数器就减一;当一个对象的计数器值为0时,就说明没用任何引用再引用它,那么就需要回收它;但是引用计数法难以解决循环引用问题,至使JVM无法识别它是垃圾对象,就无法进行回收。
可达性分析法:现在的虚拟机一般都用的可达性分析法来判断对象是否要被回收;具体的算法就是,通过一系列的GC Roots 的对象为起始点,从这些节点往下搜索,搜索到走过的路径为引用连,当一个对象没有在任何GC Roots的引用链上时,也就说明这个对象无用,要被垃圾回收
finalize
对象的自我拯救:GC 标记之后,清理之前会判断对象是否重写了finalize()方法,如果重写了将执行该对象的finalize()方法,可在该方法中将这个对象重新引用给GC Root对象实现这个对象的一次自我救赎。如果该类没有重写该方法,或者重写了但是没有赋给可达对象,这个对象也是会被认为是垃圾对象。而且对象的该方法只会执行一次,如果第二次这个对象被标记为垃圾对象,将不再执行该方法,就会被回收。
附: finalize()方法具体细节: 如果一个对象没有一条引用链经过该对象,就会对它进行第一次标记和进行一次筛选。
筛选就是看这个对象有没有必要执行finalize方法。当一个对象没有覆盖finalize方法或者已经执行过了 finalize方法,虚拟机都会视为没有必要执行;
当虚拟机认为有必要执行时,就会把这个对象放入一个叫F-Queue的队列中,并在稍后由一个虚拟机自动建成的一个优先级很低的线程去执行在这队列中所有对象的finalize方法。
因为JVM创建的是一个优先级特别低的线程去执行它,而且也为了确保不因为finalize方法中的错误而影响垃圾回收,所有Java并不会保证finalize方法会被及时执行,而确也不会保证他们会被执行完。
所以在平常的开发中,还是要避免使用finalize方法,有需要关闭资源等需求还是尽量使用try-finaly来实现。
直接内存的垃圾回收:对于Java的直接内存,是独立于堆内存之外的内存区域,它不由JVM进行管理,而由操作系统来进行管理,但是JVM在Full GC时也会对其进行垃圾回收。但是它不会像新生代和老年代一样,当内存满了会告诉JVM要进行垃圾回收,直接内存只能是跟着堆空间的Full gc顺带着一起垃圾回收,所有在很多时候直接内存会满而没办法及时的回收。解决办法只能是当它抛出异常时,及时catch到,然后立马调用System.gc();通知虚拟机进行全局的垃圾回收。
四、GC时做什么事情?
除了之前说的GC时会清理不适用的对象之外,还有些比较细节的。
(停止其他线程) 虚拟机在从GC roots搜索可以可达的对象时,是需要将其他线程都挂起,因为在分析GC Roots对象和分析可达对象时对象的引用关系可能发生改变,这样的话分析结果就没有办法得到保证,所以GC进行的时候其他线程都要挂起等待GC结束。(不包括JNI调用的线程,因为其调用的线程所产生的垃圾由调用线程的语言维护)
安全点
安全区域
老年代碎片整理
五、GC调优
- 线程不易的过多
- 内存区域大小使用参数调节
- 直接内存的垃圾回收
CMS
CMS,全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性需求 大于对吞吐量的要求,能够承受垃圾回收线程和应用线程共享处理器资源,并且应用中存在比较多的长生命周期的对象的应用。CMS是用于对tenured generation的回收,也就是年老代的回收,目标是尽量减少应用的暂停时间,减少full gc发生的几率,利用和应用程序线程并发的垃圾回收线程来标记清除年老代。在我们的应用中,因为有缓存的存在,并且对于响应时间也有比较高的要求,因此希 望能尝试使用CMS来替代默认的server型JVM使用的并行收集器,以便获得更短的垃圾回收的暂停时间,提高程序的响应性。
CMS收集周期
CMS并非没有暂停,而是用两次短暂停来替代串行标记整理算法的长暂停,它的收集周期是这样:
初始标记(CMS-initial-mark) -> 并发标记(CMS-concurrent-mark) -> 重新标记(CMS-remark) -> 并发清除(CMS-concurrent-sweep) ->并发重设状态等待下次CMS的触发(CMS-concurrent-reset)。
其中的1,3两个步骤需要暂停所有的应用程序线程的。第一次暂停从root对象开始标记存活的对象,这个阶段称为初始标记;第二次暂停是在并发标记之后, 暂停所有应用程序线程,重新标记并发标记阶段遗漏的对象(在并发标记阶段结束后对象状态的更新导致)。第一次暂停会比较短,第二次暂停通常会比较长,并且 remark这个阶段可以并行标记。
而并发标记、并发清除、并发重设阶段的所谓并发,是指一个或者多个垃圾回收线程和应用程序线程并发地运行,垃圾回收线程不会暂停应用程序的执行,如果你有多于一个处理器,那么并发收集线程将与应用线程在不同的处理器上运行,显然,这样的开销就是会降低应用的吞吐量。Remark阶段的并行,是指暂停了所有应用程序后,启动一定数目的垃圾回收进程进行并行标记,此时的应用线程是暂停的。
CMS的young generation的回收采用的仍然是并行复制收集器,这个跟Paralle gc算法是一致的。