[资料] [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 写道
这篇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/看是不是好一些。
|