[讨论] HandlePromotionFailure

imfox 2014-10-23
如果老年代垃圾收集器使用CMS,如果在minor GC时,监测到之前每次晋升到老年代的平均大小大于年老代的剩余空间大小,那么是发生CMS并发GC呢,还是serial Full GC?
RednaxelaFX 2014-10-24
楼主问题的描述并不是handle promotion failure的功能,而是在那之前的问题吧。

ParNew/CMS组合在一起用的时候,
ParNewGeneration::collect():
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/gc_implementation/parNew/parNewGeneration.cpp#l948
  // If the next generation is too full to accommodate worst-case promotion
  // from this generation, pass on collection; let the next generation
  // do it.
  if (!collection_attempt_is_safe()) {
    gch->set_incremental_collection_failed();  // slight lie, in that we did not even attempt one
    return;
  }


collection_attempt_is_safe()的定义在基类DefNewGeneration里:
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/memory/defNewGeneration.cpp#l865
这里它基本上就是交给CMS的ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes)去计算最大可能晋升的对象大小跟CMS generation剩余空间的关系:
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp#l898
bool ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
  size_t available = max_available();
  size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  bool   res = (available >= av_promo) || (available >= max_promotion_in_bytes);
  return res;
}

也就包含了楼主所问的检查:之前平均晋升的对象大小、当前young generation已使用的大小(也就是最大可能晋升的对象大小)跟当前CMS的最大可用空间大小相比较。只要CMS的剩余空间比前两者的任意一者大,CMS就认为晋升还是安全的;反之亦然。

楼主所问的“之前每次晋升到老年代的平均大小大于年老代的剩余空间大小”如果外加“当前minor GC时ParNew里已使用空间同样大于CMS年老代的剩余空间大小”,那么ParNew的minor GC就会在通知CMS说“我不干了”之后直接跳过自己。

这样就会回到GenCollectedHeap::do_collection()的循环里。GCH发现ParNew不干了之后会问CMS年老代要不要收集,CMS基本上只能回答:要。
然后CMS会根据UseCMSCompactAtFullCollectionCMSFullGCsBeforeCompaction和当前收集状态去决定是:(1) 使用跟Serial Old GC一样的LISP2算法的mark-compact来做full GC,还是 (2) 用CMS自己的mark-sweep来做不并发的(串行的)old generation GC。后者在CMS里也叫做foreground collector(正常的并发模式的CMS是background collector)。
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp#l1933

如果发生晋升失败的时候CMS已经正在执行一个并发收集,那么那个并发收集就会被打断,各种状态复位之后转为执行上面选择的两种串行收集之一。

UseCMSCompactAtFullCollection默认为true,CMSFullGCsBeforeCompaction默认是0,这样的组合保证CMS默认不使用foreground collector,而是用Serial Old GC的方式来在promotion failed或者可能会fail的时候进行full GC。
JDK9的一个新变更就要去掉这两个参数及其背后的代码,彻底去掉CMS的foreground GC功能:JEP 214: Remove GC Combinations Deprecated in JDK 8。为了与未来接轨大家也请不要乱配置这俩参数了喔。

要注意上述两种可能性不但算法不一样,收集的范围也不一样。

Serial Old GC的算法是mark-compact(也可以叫做mark-sweep-compact,但要注意它不是“mark-sweep”)。具体算法名是LISP2。它收集的范围是整个GC堆,包括Java heap的young generation和old generation,以及non-Java heap的permanent generation。因而它是full GC。

CMS的foreground collector的算法就是普通的mark-sweep。它收集的范围只是CMS的old generation,而不包括其它generation。因而它在HotSpot VM里不叫做full GC。

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

顺带一提,如果在ParNew准备收集时CMS说晋升没问题,但ParNew已经开始收集之后确实遇到了晋升失败的情况,最终的处理跟上面的流程基本一样。
imfox 2014-10-24
多谢R大的回复,从源代码级别解答问题,之前一直以为full GC只是old区的,原来是整个heap的~
imfox 2014-10-24
还有另外个一个问题:concurrent mode failure有没有可能是正在CMS GC的时候发生了Promotion Failure?怎么区分这两种failure呢?

RednaxelaFX 写道
楼主问题的描述并不是handle promotion failure的功能,而是在那之前的问题吧。

ParNew/CMS组合在一起用的时候,
ParNewGeneration::collect():
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/gc_implementation/parNew/parNewGeneration.cpp#l948
  // If the next generation is too full to accommodate worst-case promotion
  // from this generation, pass on collection; let the next generation
  // do it.
  if (!collection_attempt_is_safe()) {
    gch->set_incremental_collection_failed();  // slight lie, in that we did not even attempt one
    return;
  }


collection_attempt_is_safe()的定义在基类DefNewGeneration里:
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/memory/defNewGeneration.cpp#l865
这里它基本上就是交给CMS的ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes)去计算最大可能晋升的对象大小跟CMS generation剩余空间的关系:
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp#l898
bool ConcurrentMarkSweepGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
  size_t available = max_available();
  size_t av_promo  = (size_t)gc_stats()->avg_promoted()->padded_average();
  bool   res = (available >= av_promo) || (available >= max_promotion_in_bytes);
  return res;
}

也就包含了楼主所问的检查:之前平均晋升的对象大小、当前young generation已使用的大小(也就是最大可能晋升的对象大小)跟当前CMS的最大可用空间大小相比较。只要CMS的剩余空间比前两者的任意一者大,CMS就认为晋升还是安全的;反之亦然。

楼主所问的“之前每次晋升到老年代的平均大小大于年老代的剩余空间大小”如果外加“当前minor GC时ParNew里已使用空间同样大于CMS年老代的剩余空间大小”,那么ParNew的minor GC就会在通知CMS说“我不干了”之后直接跳过自己。

这样就会回到GenCollectedHeap::do_collection()的循环里。GCH发现ParNew不干了之后会问CMS年老代要不要收集,CMS基本上只能回答:要。
然后CMS会根据UseCMSCompactAtFullCollectionCMSFullGCsBeforeCompaction和当前收集状态去决定是:(1) 使用跟Serial Old GC一样的LISP2算法的mark-compact来做full GC,还是 (2) 用CMS自己的mark-sweep来做不并发的(串行的)old generation GC。后者在CMS里也叫做foreground collector(正常的并发模式的CMS是background collector)。
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/312b5f1dc31d/src/share/vm/gc_implementation/concurrentMarkSweep/concurrentMarkSweepGeneration.cpp#l1933

如果发生晋升失败的时候CMS已经正在执行一个并发收集,那么那个并发收集就会被打断,各种状态复位之后转为执行上面选择的两种串行收集之一。

UseCMSCompactAtFullCollection默认为true,CMSFullGCsBeforeCompaction默认是0,这样的组合保证CMS默认不使用foreground collector,而是用Serial Old GC的方式来在promotion failed或者可能会fail的时候进行full GC。
JDK9的一个新变更就要去掉这两个参数及其背后的代码,彻底去掉CMS的foreground GC功能:JEP 214: Remove GC Combinations Deprecated in JDK 8。为了与未来接轨大家也请不要乱配置这俩参数了喔。

要注意上述两种可能性不但算法不一样,收集的范围也不一样。

Serial Old GC的算法是mark-compact(也可以叫做mark-sweep-compact,但要注意它不是“mark-sweep”)。具体算法名是LISP2。它收集的范围是整个GC堆,包括Java heap的young generation和old generation,以及non-Java heap的permanent generation。因而它是full GC。

CMS的foreground collector的算法就是普通的mark-sweep。它收集的范围只是CMS的old generation,而不包括其它generation。因而它在HotSpot VM里不叫做full GC。

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

顺带一提,如果在ParNew准备收集时CMS说晋升没问题,但ParNew已经开始收集之后确实遇到了晋升失败的情况,最终的处理跟上面的流程基本一样。

Global site tag (gtag.js) - Google Analytics