[资料] [HotSpot VM] 请教G1算法的原理

pulsar_lxl 2014-12-11
看YGC日志有一块没太明白,特别是关于Code Root的,是扫描Code中的一些静态变量么,那Fixup、Migration和Purge又分别做了什么事情呢?Redirty Cards又是干什么的,GC完了dirty card不是应该重置么,为什么要redirty?麻烦R大能帮解释一下吧,谢谢!
[Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6]
[Code Root Fixup: 0.5 ms]
[Code Root Migration: 1.3 ms]
[Code Root Purge: 0.0 ms]
[Redirty Cards: 0.0 ms]

另外,还有一个疑问,关于InitiatingHeapOccupancyPercent参数,看资料上说是已用空间占全heap超过InitiatingHeapOccupancyPercent时,会启动global concurrent marking,但是看源码是_g1->non_young_capacity_bytes(),也就是说排除了young gen,源码对应的jdk版本是1.8,难道是后来改了么?相关代码如下:
    size_t marking_initiating_used_threshold = (_g1->capacity() / 100) *    InitiatingHeapOccupancyPercent;
    size_t cur_used_bytes = _g1->non_young_capacity_bytes();
    size_t alloc_byte_size = alloc_word_size * HeapWordSize;
    if ((cur_used_bytes + alloc_byte_size) > marking_initiating_used_threshold) {
      .......
    }

RednaxelaFX 2014-12-12
@pulsar_lxl 话说我找到您的表格的出处了。是Oracle的教程吧:http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
这个教程自身并没有太错,但是要是不了解足够背景就去读就有可能会被误导(例如说以为cleanup与copy是混在一个phase里的)。

pulsar_lxl 写道
看YGC日志有一块没太明白,特别是关于Code Root的,是扫描Code中的一些静态变量么,那Fixup、Migration和Purge又分别做了什么事情呢?Redirty Cards又是干什么的,GC完了dirty card不是应该重置么,为什么要redirty?麻烦R大能帮解释一下吧,谢谢!
[Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6]
[Code Root Fixup: 0.5 ms]
[Code Root Migration: 1.3 ms]
[Code Root Purge: 0.0 ms]
[Redirty Cards: 0.0 ms]

您先看看Jenny(Yu Zhang)写的一篇G1概念介绍文然后咱们再具体聊~
https://blogs.oracle.com/g1gc/entry/g1_gc_glossary_of_terms

Code Root就是从JIT编译的代码里内嵌的、指向G1 heap的引用。在G1每个region的RSet里有记录一个strong_code_root_list,记录着包含指向该region的引用的nmethod。从JDK-7145569开始code root的处理开始受到关注。

Code Root Fixup记录的时间是运行StrongRootsScope的析构函数的耗时。里面会调用到nmethod::oops_do_marking_epilogue()来清理code root marked列表,以及把嵌在代码里的引用修正到evacuate之后新对象的位置。

Code Root Migration的意思是说,原本某个code root可能指向region A,经过evacuation之后对象移动了位置跑到region B了,那么相应的就得把这个code root加到region B的RSet去。

Code Root Purge是JDK8新加的。这是把不再引用某个region的nmethod从RSet里记录的code roots清除掉的动作。这个动作应该是在JDK-8035406加的。

Redirty cards,如果我没理解错的话是为了保持evacuation与当前可能正在并发进行的concurrent marking之间的一致性:concurrent marking过程中所有引用关系的改变都要通过card table记录下来,evacuation会移动对象因而改变了引用关系,为了把这个信息传递给concurrent marking,就把evacuation改变了的引用记录到card table去。

pulsar_lxl 写道
另外,还有一个疑问,关于InitiatingHeapOccupancyPercent参数,看资料上说是已用空间占全heap超过InitiatingHeapOccupancyPercent时,会启动global concurrent marking,但是看源码是_g1->non_young_capacity_bytes(),也就是说排除了young gen,源码对应的jdk版本是1.8,难道是后来改了么?相关代码如下:
size_t marking_initiating_used_threshold = (_g1->capacity() / 100) * InitiatingHeapOccupancyPercent;
size_t cur_used_bytes = _g1->non_young_capacity_bytes();
size_t alloc_byte_size = alloc_word_size * HeapWordSize;
if ((cur_used_bytes + alloc_byte_size) > marking_initiating_used_threshold) {
}


InitiatingHeapOccupancyPercent在我们的黑话里简称IHOP(读作eye-hop)。这个参数大体的意义在分代式G1应该从来没变过:整个G1 heap的使用量百分比。
_g1->capacity() /* 整个G1 heap的容量 */ / 100) * InitiatingHeapOccupancyPercent


您的疑问可能来自两点:

1、“使用量”的定义改变了。确实,在JDK-6976060之前,触发G1 global concurrent marking的“使用量”是整个G1 heap的使用量,包括young gen和old gen;而在JDK-6976060之后则变成了现在这样,计算的“使用量”是old gen(包括humongous region)的容量

改变之后,G1触发global concurrent marking的条件变得更加关心old gen什么时候会变得无法扩张,而不只是简单的看整堆剩余容量。毕竟global concurrent marking的目的是为了让G1 mixed GC可以找出适合的old gen region来收集,必须在old gen变得无法扩张(也就基本无法收集)之前完成marking。

2、HotSpot VM里CMS和G1使用看起来相似的参数来配置启动GC的时机,但两者的用法不一致。
G1是将-XX:InitiatingHeapOccupancyPercent解读为整个G1 heap的空间使用量百分比。

而CMS则是将-XX:CMSInitiatingOccupancyFraction解读为old gen的空间使用量百分比,不包括young gen。
bool ConcurrentMarkSweepGeneration::should_concurrent_collect() const {
  if (occupancy() > initiating_occupancy()) {
    return true;
  }
  // ...
}

而ConcurrentMarkSweepGeneration::occupancy()的定义就是:
double occupancy() const { return ((double)used())/((double)capacity()); }

这是只包括CMS old gen的使用比。
pulsar_lxl 2014-12-12
RednaxelaFX 写道
@pulsar_lxl 话说我找到您的表格的出处了。是Oracle的教程吧:http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html
这个教程自身并没有太错,但是要是不了解足够背景就去读就有可能会被误导(例如说以为cleanup与copy是混在一个phase里的)。

是的,我就是被误导了。。。

RednaxelaFX 写道

您先看看Jenny(Yu Zhang)写的一篇G1概念介绍文然后咱们再具体聊~
https://blogs.oracle.com/g1gc/entry/g1_gc_glossary_of_terms

这篇blog我看过了,不过我更喜欢R大的解释,会更透彻一些

RednaxelaFX 写道

Code Root就是从JIT编译的代码里内嵌的、指向G1 heap的引用。

对于这句话,我还不太明白,为什么只是JIT编译的代码里内嵌的,klass就不可能有么?这里的引用是指的是类变量么?

RednaxelaFX 写道

1、“使用量”的定义改变了。确实,在JDK-6976060之前,触发G1 global concurrent marking的“使用量”是整个G1 heap的使用量,包括young gen和old gen;而在JDK-6976060之后则变成了现在这样,计算的“使用量”是old gen(包括humongous region)的容量

改变之后,G1触发global concurrent marking的条件变得更加关心old gen什么时候会变得无法扩张,而不只是简单的看整堆剩余容量。毕竟global concurrent marking的目的是为了让G1 mixed GC可以找出适合的old gen region来收集,必须在old gen变得无法扩张(也就基本无法收集)之前完成marking。

对,是问使用量定义的改变,这个改变是使global concurrent marking更聚焦old gen了,不过我发现有个问题,就是我在使用G1的时候,按照一些调优指南的建议,没有设置新生代的大小,后来发现新生代的大小稳定在1.9g(Xmx3g),也就是说对于默认的IHOP(45%),永远不可能达到,所以造成的后果是只有YGC和full gc。。。


RednaxelaFX 2014-12-12
pulsar_lxl 写道
RednaxelaFX 写道

Code Root就是从JIT编译的代码里内嵌的、指向G1 heap的引用。

对于这句话,我还不太明白,为什么只是JIT编译的代码里内嵌的,klass就不可能有么?这里的引用是指的是类变量么?

Code root专指JIT编译出来的代码里内嵌的对象引用。Klass不是code。
您会看到所谓klass的strong root是在扫描SystemDictionary的时候处理的,而code root则是在扫描CodeCache的时候处理的。

JIT编译出来的代码可以直接在代码内嵌常量引用。例如:
public static boolean isFoo(String str) {
  return str == "foo";
}

这样的代码被JIT编译之后可能得到(下面示意):
  cmp rsi, 0x7123400  ;; 内嵌引用; 0x7123400指向"foo"实例
  jne Lne
  mov rax, 1
  ret
Lne:
  xor rax, rax
  ret

这种直接嵌在代码里的常量是code root。

pulsar_lxl 写道
不过我发现有个问题,就是我在使用G1的时候,按照一些调优指南的建议,没有设置新生代的大小,后来发现新生代的大小稳定在1.9g(Xmx1g),也就是说对于默认的IHOP(45%),永远不可能达到,所以造成的后果是只有YGC和full gc。。。

orz!
这在最新的JDK8上仍然可以重现么?能的话这太悲催了,去报bug吧
(您是说young gen稳定在0.9GB而-Xmx1G?)
pulsar_lxl 2014-12-12
RednaxelaFX 写道

orz!
这在最新的JDK8上仍然可以重现么?能的话这太悲催了,去报bug吧
(您是说young gen稳定在0.9GB而-Xmx1G?)

手抖了,Xmx3g,已更新。
我用的jdk版本是1.8.0_20

不过我只观察了一天的gc日志,还需要在多看看

pulsar_lxl 2014-12-15
观察了更多的GC日志,发现了global concurrent marking了。
66955.259: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: requested by GC cause, GC cause: G1 Humongous Allocation]

不过是由Humongous Allocation引起的,我调整了G1HeapRegionSize,然后再观察观察。

不过需要R大再帮忙解释一个问题,G1中的预测模型公式有什么论证么,为什么采用这个公式?谢谢!!
   double get_new_prediction(TruncatedSeq* seq) {
          return MAX2(seq->davg() + sigma() * seq->dsd(),
                seq->davg() * confidence_factor(seq->num()));
    }
RednaxelaFX 2014-12-15
嗯楼上请先读读这个:http://www.narihiro.info/g1gc-impl-book/scheduling.html
技术书的日语很好读的,机翻也可以八九不离十。先试试读读看。
tracybryant 2014-12-15
RednaxelaFX 写道
嗯楼上请先读读这个:http://www.narihiro.info/g1gc-impl-book/scheduling.html
技术书的日语很好读的,机翻也可以八九不离十。先试试读读看。

 
这么好的书完全看不懂,好可惜
pulsar_lxl 2014-12-16
RednaxelaFX 写道
嗯楼上请先读读这个:http://www.narihiro.info/g1gc-impl-book/scheduling.html
技术书的日语很好读的,机翻也可以八九不离十。先试试读读看。


同楼上,google翻译的路还是任重而道远。。。
引用

衰减偏差值

与平均衰减结合衰减方差(腐烂方差)值称为我们将继续计算。让我们来看看的计算方法。
清单12.6:衰减分散测定红宝石
DAVG = 30
DVAR = 0
DAVG = 35 * 0.3 + DAVG * 0.7
DVAR =((35  -  DAVG)** 2)* 0.3 + DVAR * 0.7
DAVG = 40 * 0.3 + DAVG * 0.7
DVAR =((40  -  DAVG)** 2)* 0.3 + DVAR * 0.7
DAVG = 42 * 0.3 + DAVG * 0.7
DVAR =((42  -  DAVG)** 2)* 0.3 + DVAR * 0.7
DAVG = 50 * 0.3 + DAVG * 0.7
DVAR =((50  -  DAVG)** 2)* 0.3 + DVAR * 0.7
dvar.to_i#=> 40
分散性和是一个值,指示从基准值的距离。这种情况的基准值将是“的添加的数据的时间的整个历史的衰减平均值”。我衰减平均值预测值(=什么有望成为下一个数)。这意味着它们的方差此时我的意思是“从预测值或实际数据当时是多少远”。进一步分散,可以计算通过逐渐衰减过去的数据在相同的方式衰减平均的影响。
那么,是方差衰减的平方根衰减标准偏差(腐旧的标准偏差)我问。
清单12.7:由红宝石测定衰减标准偏差
的Math.sqrt(DVAR).to_i#=> 6
这里得到衰减的标准偏差表示的实际数据和预测值的变动。因此实际值从预测值是不可能预测的正,负6的范围内会发生波动。
RednaxelaFX 2014-12-16
噗!那个⋯诶。楼上试试http://honyaku.yahoo.co.jp/url/看是不是好一些。
Global site tag (gtag.js) - Google Analytics