《深入理解java虚拟机》总结
垃圾收集(Garbage Collection),最主要的就是下面三件事情:
- 哪些对象需要回收
- 什么时候回收
- 怎么回收
对象是否存活
根据对象是否存活,确定哪些对象是需要回收的
可达性算法
判断对象是否存活主要使用的是可达性分析算法,是从Gc Roots作为根节点,根据引用关系向下搜寻,如果对象能被搜寻到,那么这些对象就是活着的。
引用
分为强引用(Strongly reference),软引用(soft reference),弱引用(weak reference),虚引用(phantom reference)。他们在垃圾回收时的现象是不一样的。 只要还有强引用是不会被垃圾回收器回收的;软引用是在将要发生内存溢出的时候会进行收集;弱引用只能生存到下一次垃圾回收发生时;为一个对象设置虚引用的唯一目的只是在对象被垃圾回收器回收时收到一个系统通知。
垃圾回收算法
在zgc出现之前,大多数的垃圾收集器都是遵循分代收集的,主要是由于下面两个假说:
- 绝大多数的对象都是朝生夕灭的
- 能扛过多次垃圾回收的对象都是不容易消亡的。
算法分类
java堆被分为新生代,老年代。根据不同的区域的特性产生了:
-
标记清除
-
标记复制
-
标记整理
各有优劣,标记清除快速,但是会产生内存碎片;标记复制,会有部分空间的浪费;标记整理一般很难与用户线程并发,也就是说需要Stop the world(ZGC实现了并发的整理算法)
垃圾收集器
垃圾收集器是实现了垃圾回收算法
Serial
单线程垃圾回收器,而且在收集的时候会 “Stop the world”。这是Hotpot虚拟机在客户端模式下默认的新生代收集器,因为足够简单,而且由于管理的内存一般不会特别大,造成的几十ms的停顿一般是可以忍受的。
Parallel New
就是上述的Serial的多线程版本,主要是它能够跟CMS配合工作,作为新生代的垃圾收集器才获得了较多的应用。
Parallel Scavenge
主要关注的是吞吐量,而且还能够实现垃圾回收的自适应调节策略来调整参数。
serial old
主要是作为客户端模式下的虚拟机使用。也可以配合Parallel Scavenge使用。还能作为CMS在发生Concurrent Mode Failure时不久措施。
Parallel Old
搭配Parallel Scavenge使用,注重吞吐量优先。
CMS
第一次实现了Gc线程与用户线程并发。主要是以下几个阶段:
- 初始标记
- 并发标记
- 最终标记
- 并发清除
初始标记和并发标记阶段还是要Stop The World,但是初始标记只与Gc Roots有关,最终标记也只需要使用增量更新(并发标记阶段用户线程改变的对象)。
很明显存在以下几点缺点:
- 对处理器资源比较敏感,分出一些线程进行垃圾回收,降低吞吐量
- 无法处理浮动垃圾,可能发生Concurrent Mode Failure,这时只能够采用Serial Old来进行补救。
在CMS的并发标记和并发清除阶段,用户线程还是在继续运行,那么还会产生新的垃圾对象,有些是在标记过程结束后的,只能留在下次进行清理。这就是浮动垃圾。而且还需要预留一定的空间供用户线程继续使用,-XX:CMSInitiatingOccupancyFraction参数可以设置在老年代空间使用到什么程度的时候进行垃圾回收。
- 标记清除算法都会造成内存碎片。标记整理算法很难实现与用户线程并发(ZGC实现了)
G1垃圾收集器
G1垃圾收集器开创了基于Region的堆内存布局,也是开创了不是一次要回收所有的堆内存,在后台维护一个对Region的回收优先级列表,优先回收那些价值大的区域,这也就是Garbage First(G1)名字的由来。主要是以下几个阶段:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收 但是存在以下几点困难:
- 跨region引用问题,使用的是记忆集,只是这儿的region太多,记忆集的占用的内存比较高,甚至能占用20%,CMS就只需要维护一份从老年代到新生代的记忆集就可以
- 使用原始快照来解决并发标记阶段用户线程改变对象引用关系导致标记可能出错的情况
- 计算出衰减平均值来评估region的回收时间。++MaXGCPauseMillis只是垃圾回收停留的期望值。
与CMS比较:
- G1垃圾收集器是基于并发清理的,但是从局部来看又是标记复制的(直接把收集region的存活对象复制到其他region),不会产生内存碎片
- G1垃圾收集器的内存占用和额外负载都会比CMS高
记忆集,G1每个region都会之间都需要记忆集记录引用关系 维护记忆集时需要使用到写屏障,都会使用到写后屏障。G1采用原始快照还需要写前屏障,造成额外的消耗比较大。