深入理解java虚拟机之自动内存管理机制(三)
各类垃圾收集器与GC日志
(一)垃圾收集器
一、Serial收集器
最基本、历史最悠久的收集器。使用复制算法,用在新生代,通常老年代用Serial old配合。GC过程需要stop the world。适用于client模式下的虚拟机。
二、ParNew收集器
Serial多线程版本,采用复制算法时,开启多线程进行复制。能与Serial old配合,且是唯一能与cms收集器配合使用的新生代收集器。适用于server模式下的虚拟机。在多cpu的环境
下效果更好。
三、Parallel Scavenge收集器
关注点是达到一个可控制的吞吐量,使用复制算法。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。
四、Serial Old收集器
Serial收集器的老年代版本,单线程收集器,使用标记-整理算法,适用于Client模式下的虚拟机。
五、Parallel Old收集器
Parallel Scavenge老年代版本,使用标记-整理算法。
六、CMS收集器
以获得最短回收停顿时间为目标的收集器。运作过程分为4个步骤。
1. 初始标记。该阶段就是标记一下GC Roots能直接关联的对象。
2. 并发标记。就是GC Roots tracing,该过程可与用户线程并发执行。
3. 重新标记。修正并发标记期间发生变动的对象的标记。
4. 并发清除。该过程可与用户线程并发执行。
缺点:
1. 对cpu资源敏感。如果不是多核,则由于存在两个并发过程,会导致用户程序执行缓慢。
2. 无法处理浮动垃圾,并且由于在清除阶段与用户线程并发执行,可能由于内存不足而导致一次full gc的产生。所谓浮动垃圾,就是在重新标记过后产生的垃圾。解决办法是预留足够内存。
3. 由于采用标记-清除算法,所以容易产生大量内存碎片。
七、G1收集器
G1是革命性的。它有以下几个特点。
1. 并行与并发。G1可以让本来需要stw的动作通过并发的方式让java程序继续执行。
2. 分代收集。G1收集器自身就可以管理新生代和老年代的对象。
3. 空间整合。整体使用标记-整理算法,局部使用复制算法。
4. 可预测的停顿。G1会建立可预测的停顿时间模型。
G1与传统收集器不同的地方:
G1将java堆分为许多个大小相同的独立区域(Region),新生代和老年代之间不再是物理隔离的,它们都是一部分Region的集合。
G1为什么能建立可预测的停顿时间模型?
G1可以有计划地避免在Java堆的进行全区域的垃圾收集,通过跟踪各个Region获得其收集价值大小(回收所获得的空间大小以及回收所需要的时间的经验值),在后台维护一个优先列表;
每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来),这就保证了在有限的时间内可以获取尽可能高的收集效率。
G1如何解决一个对象被不同区域引用的问题?
不止G1要解决这个问题,其他收集器也要解决这个问题,其他收集器需要解决新生代与老年代之间的对象引用问题。都是通过使用Rememed Set来避免全堆扫描。G1的每个Region都有一
个与之对应的Remembered Set,虚拟机发现程序在对引用类型的数据进行读写操作时,会产生一个Write Barrier暂时中断写操作,检查引用类型引用的对象是否处于不同的Region之中,如果是,就
通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在Gc根节点枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏。
关于OopMap 和 Remembered Set
总的来说:OopMap用于枚举根节点,Remembered Set 用来作可达性分析。
OopMap避免了全栈扫描,Remembered Set避免了全堆扫描。
新生代 GC(发生得非常频繁)。一般来说, GC过程是这样的:首先枚举根节点。根节点有可能在新生代中,也有可能在老年代中。这里由于我们只想收集新生代(换句话说,不想收集老年代),
所以没有必要对位于老年代的 GC Roots 做全面的可达性分析。但问题是,确实可能存在位于老年代的某个 GC Root,它引用了新生代的某个对象,这个对象你是不能清除的。那怎么办呢?
与OopMap一样,用空间换时间,用一个数据结构保存这种引用信息,这样在只需要再新生代上利用这两个东西就能完成可达性的分析。RememberedSet记录的是新生代的对象被老年代引用的关系。
所以“新生代的 GC Roots ” + “ RememberedSet 存储的内容”,才是新生代收集时真正的 GC Roots 。
参考:https://dsxwjhf.iteye.com/blog/2201685
https://blog.csdn.net/ifleetingtime/article/details/78934379
G1的运作过程(不计维护Remembered Set)
1. 初始标记(Initial Marking)
仅标记一下GC Roots能直接关联到的对象;
且修改TAMS(Next Top at Mark Start),让下一阶段并发运行时,用户程序能在正确可用的Region中创建新对象;
需要"Stop The World",但速度很快;
2. 并发标记(Concurrent Marking)
进行GC Roots Tracing
耗时较长,但与用户线程并发执行;
3. 最终标记(Final Marking)
为了修正并发标记期间变动的对象的标记记录
上一阶段对象的变化记录在线程的Remembered Set Log;
这里把Remembered Set Log合并到Remembered Set中;
需要"Stop The World",且停顿时间比初始标记稍长,但远比并发标记短;
采用多线程并行执行来提升效率;
4. 筛选回收(Live Data Counting and Evacuation)
首先排序各个Region的回收价值和成本;
然后根据用户期望的GC停顿时间来制定回收计划;
最后按计划回收一些价值高的Region中垃圾对象;
回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;
(二)GC日志
https://blog.csdn.net/xiaodu93/article/details/61926114