深入理解Java虚拟机(三)
我们主要介绍一下垃圾收集器。本章主要介绍经典垃圾收集器
如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。
新生代收集器
1、Serial收集器 (Serial中文翻译为:依次的,顺序的)
- 大家只看名字就能够猜到,这个收集器是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
- 简单而高效(与其他收集器的单线程相比)
- 客户端模式默认收集器
- 采用复制算法实现
2、ParNew收集器
- ParNew收集器实质上是Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括Serial收集器可用的所有控制参数(例如:-XX:SurvivorRatio、-XX: PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规 则、回收策略等都与Serial收集器完全一致
- 并行(Parallel):并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线 程在协同工作,通常默认此时用户线程是处于等待状态。
- 对cpu的依赖性比较高
- 采用的依然是复制算法
3、 Parallel Scavenge收集器
- Parallel Scavenge收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器,也是 能够并行收集的多线程收集器
- 关注点在于吞吐量
- 具有自适应的调节策略(GC Ergonomics)
老年代收集器
1、Serial Old收集器
- Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
- CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用。
- 具体流程见上图Serial收集器中运行图
2、Parallel Old收集器
- Parallel Old是Parallel Scavenge收集器的老年代版本,支持多线程并发收集,基于标记-整理算法实现。
- 注重吞吐量或者处理器资源比较稀缺的场合
3、CMS收集器 (重点)
-
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
-
基于标记-清除算法实现
-
运作过程分为四个步骤
- 初始标记 (Stop The World,用户线程被终止)
- 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;
- 并发标记
- 并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对 象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;
- 重新标记 (Stop The World)
- 而重 新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。(停顿时间稍长于初始标记,但是远远低于并发标记)
- 并发清除
- 清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
- 初始标记 (Stop The World,用户线程被终止)
-
并发收集,低停顿(总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的)
-
对处理器资源要求比较高
-
缺点:
- 无法处理“浮动垃圾” : 在CMS的并发标记和并发清理阶 段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分 垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集 时再清理掉。这一部分垃圾就称为“浮动垃圾”。
- 会产生大量空间碎片,这是由采用的标记-清除算法导致的。
Garbage First收集器(G1收集器)(重点)
- 开创了收集 器面向局部收集的设计思路和基于Region的内存布局形式
- G1是一款主要面向服务端应用的垃圾收集器。
- 可预测停顿时间垃圾收集器
- 也是基于分代收集理论的
- G1 把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。
- G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元
- Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过了一个 Region容量一半的对象即可判定为大对象。
- G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区 域(不需要连续)的动态集合。
- 可以管理全堆内存
- G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region 之间)上看又是基于“标记-复制”算法实现
- 运行时 四个步骤
- 初始标记(Stop The World)
- 仅仅只是标记一下GC Roots能直接关联到的对象,这个阶段需要 停顿线程,但耗时很短
- 并发标记
- 从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆 里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。
- 而G1 收集器则是通过原始快照(SATB)算法来实现并发标记的。
- 原始快照(SATB):解决并发扫描时的对象消失问题的一种方案。GC Roots 扫描过程中,当灰色对象要删除指向白色对象的引用关系时,就将这个要删 除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描 一次。
- 最终标记 (Stop The World)
- 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留 下来的最后那少量的SATB记录。
- 筛选回收 (Stop The World)
- 负责更新Region的统计数据,对各个Region的回 收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region 构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。
- 初始标记(Stop The World)
内存分配和回收策略
- 对象优先在Eden去分配。
- 当Eden区没有足够空间进行分配时,虚拟机将发起 一次Minor GC。
- 大对象直接进入老年代。
- 大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者 元素数量很庞大的数组
- 长期存活的对象将进入老年代。
- GC年龄到达15岁
- 相同年龄所有对象大小总和大于Survivor空间一半,那么我们就把大于等于该年龄的对象直接进入老年代
- 空间分配担保