垃圾收集器
可以根据分代不同,设定不同的收集器。有时候需要分别为新生代或老年代选用合适的收集器。
一般来说,新生代收集器的收集频率较高,应选用性能高效的收集器;而老年代收集器收集次数相对较少,对空间较为敏感,应当避免选择基于复制算法的收集器
1 Serial收集器
- 【新生代】 收集器
- 采用复制算法
- 单个GC线程执行垃圾回收
- 垃圾回收时,用户线程暂停
2 CMS收集器
- 并发标记清楚收集器(CMS的GC线程可以和用户线程一起工作),因此可以获得最短回收停顿时间。
2.【老年代】收集器
3. 基于标记-清除实现的。
CMS的步骤:
- 初始标记:暂停【用户程序】,标记GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。
- 并发标记:GC RootsTracing 【GC和用户】线程并发,闭包结构记录【可达对象】
- 重新标记: 需要“Stop The World”。【用户程序】在【并发标记】可能更新【引用域】,所以【暂停用户程序】修正对象【可达性】的变化。
- 并发清除:【GC】线程和【用户程序】并发,GC线程【清除可回收对象】
和串行的一样,将堆结构化分成三个部分:年轻代、老年代、固定内存大小的永久代。
stop the world以及相关原因:收集的时候,需要暂停用户程序。
CMS优点(为什么可以实现并发)
使用标记-清除的方法,对收集过程进行了细粒度的分解,然后以流水线方式拆分了收集周期。将耗时长的操作单元保持与应用线程并发执行。只将那些必需STW才能执行的操作单元单独拎出来,控制这些单元在恰当的时机运行,并能保证仅需短暂的时间就可以完成。
CMS缺点
- 【cpu资源敏感】 : 并发期间,GC程序占用CPU资源,导致用户程序吞吐量低。
- 【无法处理浮动垃圾】 : 【并发清除】阶段,【GC和用户】程序并发,在该阶段仍会产生垃圾。这些垃圾仅能在下一次GC中回收。
- 【标记-清除】算法,造成 大量【不连续】空间碎片(怎么并发清除的?)
3 G1收集器
在G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不是。
G1引入了region概念,每个region都是连续的虚拟内存范围。region中又分成了eden, suvivor, old。
region的大小可以在JVM启动的时候选择。大约两千个region。
这样收集的时候,不需要在全堆范围内执行。
G1优点
- 并发 : 利用多核处理器,既实现用户程序停顿时间短,又一定程度保障CPU吞吐量。
- 分代收集 : G1收集器【无需其他收集器配合】,独立管理堆。但存在分代收集概念
- 空间整合 : G1收集器 【整体】采用 【标记-整理】,【局部】采用 【复制算法】。不产生空间碎片
- 可预测停顿: G1建立预测模型,预测停顿时长
可预测的停顿时间模型: 可以【划分region】收集,因此可避免在【整个Java堆】中进行全区域的垃圾收集。该模型能算出每个Region的收集成本并量化,因此【收集器给定停顿时间后】,可以选择恰当的region作为收集目标。
G1流程
- 初始标记 : 【暂停用户程序】,标记和root相连的对象
- 并发标记 : 【GC和用户】程序并发,GC程序 标记 可达对象
- 最终标记 : 修正 【并发标记】阶段,【用户程序更新引用域】所 引起的【可达性】变化
- 筛选回收 : 对各个Region的【回收价值和成本进行排序】,根据用户所期望的GC停顿时间来制定回收计划(与用户程序并发)。只回收一部分Region,时间是用户可控制的。
相比于CMS:消除了碎片,简化收集器的各个部分。但是无法像CMS一样让回收线程和用户线程并发进行。回收时候需要暂停用户线程
GC时为什么要暂停用户线程?首先,如果不暂停用户线程,就意味着期间会不断有垃圾产生,永远也清理不干净。其次,用户线程的运行必然会导致对象的引用关系发生改变,这就会导致两种情况:漏标和错标。
每个region区域进行单独垃圾回收。记录每个region垃圾回收的时间以及回收获得的空间,维护一个优先列表。每次优先回收价值最大的Region
执行流程:
对象一开始优先进入Eden中。当 eden 区没有足够空间进行分配时,虚拟机将发起一次 Young GC
经过一次垃圾回收后,存活下来的被放到suvivor0, suvivor1中,并且年龄+1
年龄达到一定的预置(默认为15)。
另外,大对象先进入老年区(因为大对象的复制会消耗很多)
Old GC
如果统计信息说,young gen待晋升(要移动到old gen)的数据比old gen空间大,则会出发full GC
垃圾收集的算法(其中两个)
标记-清除算法: 当内存区域满了的时候,将依旧存活的对象做标记,删除所有没有标记的对象(将产生大量的内存碎片,因此对新生代是很难接受的)
标记-复制: 将内存分成大小相同的两块。每次只使用其中一块。当其中的一块满了的时候,将存活的变量复制到另一块内存中,删除之前的整个内存。
标记-整理:和标记清除一样,但是后续操作不是直接对可回收的对象进行清理。而是让所有存活的对象都向一端移动。然后直接清除掉端边界以外的内存。
对于新生代,每次收集会死亡大量的对象,所以适合用标记-复制,对于老生代,变量占内存大,复制代价高,并且存活率高。所以适合用标记-清除
safe point
程序在safe point的时候才能stop下来,进行GC。
安全点的初始目的并不是让其他线程停下,而是找到一个稳定的执行状态。在这个执行状态下,Java虚拟机的堆栈不会发生变化。
gc发生时,会设置一个标志。线程每到安全点的时候,回去轮训终端标志,如果是true, 就会中断。
安全区域:
指在一段代码片段中,引用关系不会发生变化。在这个区域中任意地方开始GC都是安全的。也可以把Safe Region看作是被扩展了的Safepoint。