[讨论] GC的历史综述与最新进展以及在HotSpot中的具体实现

RednaxelaFX 2014-09-03
巴海和 写道
网上有电子版吧,我这里就有,刚下的,不是扫描版,是文字版,500多页。

咳咳…关于无版权的读物的讨论就不要在HLLVM群组发了。非要讨论的话私下进行吧。
RednaxelaFX 2014-09-03
ZHH2009 写道
g1从论文发布到现在10年了,未来有没有可能完全取代现有的其他gc实现?

Oracle有意向用G1 GC的框架最终一统HotSpot VM里的所有GC。当前确实只在积极开发G1了。
由于G1的SATB write barrier根本上跟high throughput/batch类型的应用场景不太兼容,未来有可能会为G1 GC加入一个“throughput mode”,以此来替代当前的ParallelScavenge。某种意义上说就是把后者硬塞到前者的框架里…

ZHH2009 写道
IBM家的JVM是否也像HotSpot这样带了这么多种GC实现让用户选择?

有呀有呀。J9 VM的GC也是在一套框架下的,与HotSpot VM的GC有颇多相似之处,毕竟是同年代的产物。其与G1 GC对应的是Balanced GC。
ZHH2009 2014-09-03
看来我也真有必要把重心放到G1了,JDK9中的G1比JDK8中的变化挺多,
目前我看到光是C++源文件就多了12个,要真看完JDK8中的G1代码似乎马上就过时了。

我也是很倾向于低层有一个统一的GC代码基,
类似J9 VM这样使用一个统一的-Xgcpolicy参数,每种策略对应不同的场景,
策略名字就体现了这样的场景:吞吐量、短停顿时间等等,
而不像HotSpot这样使用非常不直观的UseSerialGC、UseParallelGC、UseConcMarkSweepGC、UseG1GC,
这种参数名虽然把低层代码的实现方式暴露了,
但是却不能直接了当的看出是应用在哪类场景。

ZHH2009 2014-09-03
另外,感觉Oracle/Sun在G1这一块的开发进度好慢啊,花了10年才做成这样,
这么慢的原因是什么?
J9 VM的Balanced GC我还没有查到资料证实是否基于早期的G1论文开发的。
(前面那个有链接的回贴中有一个就是专门讲Balanced策略的)
ZHH2009 2014-09-03
GC这一块的新技术我把以后的研究重点放在G1和RedHat的Shenandoah

我把Shenandoah对应的openjdk8代码下下来了 http://icedtea.classpath.org/shenandoah/

Shenandoah目前的代码量很少啊,不需要Remember Set / Card Table,
看起来比G1更简单先进。
RednaxelaFX 2014-09-03
ZHH2009 写道
另外,感觉Oracle/Sun在G1这一块的开发进度好慢啊,花了10年才做成这样,
这么慢的原因是什么?

<- 花了10年做成“怎样”?ZHH大大似乎很不满嘛 >_<
GC研发本来就是个漫长的过程。基础算法可能很快就能设计并实现好,但在此之上的“策略”(policy)层面的东西才是更影响实际性能的。要让实际性能好而且稳定的好,必不可少的阶段就是慢慢对policy调优。现在的G1在其原本想定的应用场景上做的事情正是如此,慢慢把细节都打磨好。

有几个因素影响了Oracle/Sun对G1 GC的开发:
1、G1在JDK6时代产品化,从Sun Labs转交给产品组整合进HotSpot的主线代码里。这个时期正值Sun衰败,产品目标不明确而且人手严重不足。基本上就2-3个开发在做GC的工作,而且一部分精力还放到维护和调优现有的其它几个收集器上,使G1成熟得非常缓慢。
2、G1在JDK7、8时代推向一般开发者使用。Oracle收购了Sun之后,对Java的研发投入明显比Sun的末期要大,但HotSpot VM的研发人手不足情况仍然存在。GC方面,许多人力都被投入到NoPermGen项目和Java Flight Recorder项目中;而且Oracle的收购带来了一定程度的人员流失,例如G1的主力开发Tony Printezis就选择了离开Oracle,目前在Twitter研发G1。这样,这段时间在Oracle基本上只有一个有效的人头是在开发G1。
3、JDK9时代Oracle终于可以缓过劲来深入的打磨G1了。这就是我们看到的现状。

从研究项目直接转为产品,常有的问题是:研究所出来的项目很多都是要赶着发论文的,代码质量不一定很高,考虑得也不一定够周到。直接将其转移为产品(而不是根据其思想重新实现一次产品)的话,其实会有非常多的打磨工作要做。G1 GC不幸就遇到了这样的问题。

例如说,G1要发挥其性能潜力,它里面负责预测程序的内存分配/回收行为的模型必须比较准确才行。但是偏偏原本labs的版本在这块做得比较粗糙,于是早期G1的性能就不够稳定。

ZHH2009 写道
J9 VM的Balanced GC我还没有查到资料证实是否基于早期的G1论文开发的。
(前面那个有链接的回贴中有一个就是专门讲Balanced策略的)

查不到的。Balanced GC非常明显的用了跟G1 GC非常相似的实现技术,不过没有任何官方资料明确指出两者的相关性。倒是JavaOne 2012上IBM和Oracle的JVM组的一场讨论里IBM的人“非正式”的提到两者的联系——骨子里的思想一致。

这是IBM方面对那个讨论的总结:http://www.ibm.com/developerworks/cn/java/j-ibm-j1-2/index.html
引用
在 JVM Corral 的对决 (BOF6308)
演讲人:John Duimovich,Java CTO,IBM;Mikael Vidstedt,JVM 架构师,Oracle;David Keenan,Consulting Member of Technical Staff,Oracle;Ryan Sciampacone,高级软件工程师,IBM
“对决” 小组会议是一个生动而又友好的辩论会,和往年的一样。小组讨论了聚集在一起的观众提出的一些问题。
第一个问题是关于 Java 堆大于 100GB 的技术。Oracle 和 IBM 在 HotSpot 的 G1GC 和 J9 的 “平衡” 收集器中也分别提供了类似的技术。Dave Keenan 谈到了截至 Java 7 update 4 所支持的 G1GC,以及正在接受高达 3TB 的堆测试,而 Ryan Sciampacone 介绍了 IBM 产品内部在 50 - 100GB 范围内对 “平衡” 的使用,以及实际使用 500GB 的其他 IBM 客户部署。
接下来的问题与 JVM 中的多租户有关。Mikael Vidstedt 谈到,Oracle 正在对跨虚拟机共享数据的解决方案进行一些研究。John Duimovich 回答说,IBM 已经在生产 JVM 中实现了类和 JIT 数据的数据共享,并且 IBM 正致力于在单一 JVM 中运行多个隔离的应用程序。
该会议包含了一个与观众的互动讨论,内容有关命令行选项和诊断的标准化,以及 IBM 和 Oracle 如何能够更紧密地合作,并提供相同的选项和诊断。

我一时想不起他们当时讨论的细节了。有兴趣的话可以去找录音听,JavaOne官网上应该还找得到。

Balanced GC与G1相似的核心思想在于:
* Region-based。每个region有独立的remember set,可以单独收集。这样就让收集器在policy层面有充分自由来决定收集哪些region(构成所谓的collection set,简称CSet)。
* Global concurrent marking。G1采用SATB形式而Balanced采用incremental update形式,不过global marking的作用是相似的。

然后Balanced GC的collection set构成方式似乎像是早期不分代的G1与后来分代式的G1的混合体?我的感觉是跟不分代的版本更相似一些。
Balanced总是选择所有eden region,然后再选择若干个old region。这跟分代式G1的mixed GC的collection set相似。但是具体的选择依据却不完全相同。

不过作为最终产品的Balanced GC并不纯粹是G1的克隆,两者在细节层面的差异应该还挺大的。Balanced GC的模型似乎更复杂一些:
* G1 GC除了fallback用的full GC是全局in-place mark-compact的,其它的GC(例如young GC和mixed GC)都是基于evacuation的,也就是说存活对象必须拷贝到另一块region去,而不能在原本的region里直接compact。Balanced GC则可以在内存紧张的情况下做partial GC时选择对一些region做in-place mark-compact,处理边界情况可能会更好。这方面G1似乎也在改进。
* G1 GC采用的对象模型与HotSpot VM里其它GC所用的一样,没啥特别;而Balanced GC针对特大数组使用特化的arraylet来实现,对单个超大的数组处理得更好。所谓arraylet就是分段式数组,对Java透明——照样让Java程序用下标来访问数组的元素,但是实际上数组内容并不是存储在单个连续的内存块里,而是分成多块来适应region的边界。这样给了GC更大的自由去把一个超大数组切割成多个部分来处理。

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

至于Shenandoah,我还是不太看好…它的核心设计有许多硬伤实在是不能行。
rink1969 2014-09-06
G1要发挥其性能潜力,它里面负责预测程序的内存分配/回收行为的模型必须比较准确

这个在代码里面是对应自适应的功能吧?
有专门讲这个模型的文章吗?
ZHH2009 2014-09-10
RednaxelaFX 写道
Balanced GC针对特大数组使用特化的arraylet来实现,对单个超大的数组处理得更好。所谓arraylet就是分段式数组,对Java透明——照样让Java程序用下标来访问数组的元素,但是实际上数组内容并不是存储在单个连续的内存块里,而是分成多块来适应region的边界。这样给了GC更大的自由去把一个超大数组切割成多个部分来处理。


Arraylets倒是没有出现在G1的论文中,这来自于IBM的real-time收集器,
这个词有出现在下面两篇IBM发的论文中:
A Real-time Garbage Collector with Low Overhead and Consistent Utilization
A Parallel and Concurrent Real-time Compacting Garbage Collector for Multiprocessors

RednaxelaFX 写道

至于Shenandoah,我还是不太看好…它的核心设计有许多硬伤实在是不能行。

我看了An ultra-low pause time Garbage Collector for OpenJDK
因为用了Forwarding Pointer所以就不用Remembered Sets了,
有点意外的是HotSpot的DefNewGeneration中也有Forwarding Pointer
  // In the absence of promotion failure, we wouldn't look at "from-space"
  // objects after a young-gen collection.  When promotion fails, however,
  // the subsequent full collection will look at from-space objects:
  // therefore we must remove their forwarding pointers.
  void remove_forwarding_pointers();


Shenandoah还是可以保持关注,至少目前两位作者还是在不断提交代码更新的,
我只是觉得Shenandoah要成为OpenJDK的正式项目有点悬,这东西跟G1是竞争关系,
要Oracle的人不玩自家的G1改玩Shenandoah需要放弃很多。
tracybryant 2014-09-16
LeafInWind 写道
请教tracybryant,看GC handbook花了多长时间,另外是如何处理看书和看论文的关系的。我最近也刚开始看这本书,静下心看基本能看懂,但感觉看得很累。



哈,刚开始看确实很累,因为自己的研究方向是GC,所以一般看到经典的GC算法我都会尽量去找原始的论文再看一遍。不知道你为什么需要去读GC这本书,我完全是为了论文需要。其实前面R大提到的在G1中加入high throughout,我也有一点点想法,并想将其粗略实现。所以最近在看G1相关代码,GC HandBook其实我也就看了三分之一左右。后续我会全部读完。
ZHH2009 2014-10-31
CardTableModRefBS实现疑惑

对应文件: hotspot\src\share\vm\memory\cardTableModRefBS.cpp

在我的32位系统上,假设堆是20Mb,一个字(word)是4个字节,
如果堆是20Mb,那么就有 20 * (1024 * 1024) / 4 = 5242880个字

每个卡表(CardTable)对应128个字,
那么20Mb的堆就需要5242880 / 128 = 40960个卡表
(执行CardTableModRefBS::initialize()时,_guard_index就是40960)

如果操作系统的pageSize是4Kb,并且分配粒度(allocation granularity)是64Kb
所以_byte_map_size = align_size_up(_guard_index + 1, MAX2(_page_size, granularity))也是64Kb,而64Kb = 65536个字节

在执行CardTableModRefBS::dirty_MemRegion时会把byte_map_base后的某个字节设为dirty_card
也就意味着在20Mb的堆中最多只能更新65536个字段,是这个意思吗?或者是我理解错了。

================================================
2014-10-31 20:50 修正
地址右移9位(card_shift),实际上就是除以512,刚好就是128 * 4,
相当于卡表中的每个卡能代表512字节的地址空间修改情况,
为卡表额外分配的65536个字节中的某个字节并不是对应Java对象的值是否修改。

说白了就是把堆按512字节分段,每段对应卡表中的一个卡,
堆中某个分段只要改写了,卡表中对应的卡就变dirty了。
本质就是个位图,只不过这里不用位来表示,而用一个byte来代表一个卡。
分给卡表的65536个字节也不会全用到,只是为了对齐多加的,实际上就是40960个,最后一个还是guard_card。

上面修正后的理解是否对了?
对了的话,HotSpot这代码确实把好简单的设计思想掩盖太深了。
================================================ 
Global site tag (gtag.js) - Google Analytics