垃圾收集器
前言
当前商业虚拟机的垃圾收集器,多数都遵循"分代收集"的理论进行设计,分代收集名为理论,它建立在两个分代假说上:
-
弱分代假说:绝大多数对象都是朝生夕死。
-
强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分出不同的区域,然后将收集对象依据其年龄分配到不同的区域存储。在Java堆划分出不同的区域之后,垃圾收集器才可以每次只收集某个区域——因而才有了Minor GC、Major GC、Full GC这样的收集类型的划分;也才能够针对不同的区域安排与存储对象存亡特征相匹配的垃圾收集算法。
-
部分收集:指目标不是完整收集整个Java堆的垃圾收集,其中又分为
-
Young GC:指目标只是新生代的垃圾收集。
-
Old GC:指目标只是老年代的垃圾收集。目前只有CMS回收器会有单独收集老年代的行为。
-
Mixed GC:指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1回收器会有这种行为。
-
-
Full GC:收集整个Java堆和方法区的垃圾收集。
分代收集并非只是简单划分一下内存区域那么简单,对象之间不是孤立的,对象之间会存在跨代引用。
假如进行一次Young GC,但新生代中的对象完全有可能被老年代所引用,为了找出该区域的存活对象,不得不遍历整个老年代。为了解决这个问题就需要堆分代收集理论添加第三条假说
-
跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
依据这条假说,我们就不再为了少量的跨代引用而去扫描整个老年代,只需要在新生代上建立一个全局的数据结构——记忆集,这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后发生Young GC时,只有包含了跨代引用的小块内存里的对象才会加入到GC Roots进行扫描。
垃圾收集器
新生代
新生代多采用标记-复制算法
Serial
这个收集器是一个单线程工作的收集器,这里的单线程是指它进行垃圾收集时,会触发STW。
这款垃圾收集器运行在客户端模式下的默认新生代收集器,它简单高效,对于内存资源受限的环境,它是所有收集器里额外内存小号最小的;对于单核处理器或处理器核心较少的环境来说,Serial收集器由于没有线程交互的开销,可以专注于做垃圾收集。
ParNew
ParNew收集器实质上是多线程并行的Serial收集器。它支持多线程进行垃圾收集。
Parallel Scavenge收集器
Parallel Scavenge收集器的目标是达到一个可控制的吞吐量。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。
吞吐量 = {运行用户代码时间\over 运行用户代码时间 + 运行垃圾收集时间}
$$
Parallel Scavenge提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。
老年代
Serial Old
Parallel Old
支持多线程并行收集,基于标记-整理算法实现。
CMS
CMS是面向老年代,基于标记-清除算法实现的垃圾回收器。
优势 获取最短停顿时间为目标的垃圾回收器 劣势
-
无法处理“浮动垃圾”,因为在并发清除阶段会动态的产生垃圾。这就导致老年代需要预留一部分空间,如果CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现并发失败,这时虚拟机就会冻结用户线程,启用Serial Old收集器来重新进行回收。
-
标记-清除算法的劣势。
垃圾回收过程
-
初始标记:触发STW,将与GC Roots直接相连的对象标记一遍
-
并发标记:GC线程和用户线程+并行,遍历GC Roots引用链
-
重新标记:修复因并发标记过程中因为用户线程而导致标记变动
-
并发清除
整堆
G1
G1不再坚持分代区域划分,而是把连续得Java堆划分为多个大小相等的独立区域(Region),虽然G1仍然保留新生代和老年代的概念,但是新生代和老年代不再是固定的而是一系列不需要连续的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元。
G1如何解决跨代引用?
记忆集。使用记忆集避免全堆作为GC Roots扫描,记忆集会记录下别的Region指向自己的指针,并标记这些指针分别在哪些卡页的范围之内。
G1在并发标记阶段如何保证收集线程与用户线程互不干扰地运行?(因为G1没有分代区域划分的原因)
首先解决的是用户线程改变对象引用关系时,必须保证其不能打破原来的对象图结果,G1通过原始快照(SATB)算法来实现。G1为每一个Region设计了两个名为TAMS的指针,把Region中的一部分空间划分出来用于并发回收过程中的新对象分配。
垃圾回收过程
-
初始标记:触发STW,将与GC Roots直接相连的对象标记一遍,并且修改一下TAMS指针,让下一阶段用户线程并发运行时,能正确的在Region中分配新对象。
-
并发标记:从GC Roots出发进行可达性分析,扫描完成后重新处理原始快照(SATB)记录下的在并发标记阶段引用发生变化的对象。
-
最终标记:触发短暂的STW,继续处理上一阶段引用发生变化的对象标记。
-
-