常见的垃圾收集算法
标记-清除算法(Mark-Sweep)
- 首先标记出需要回收的所有对象
- 然后在标记完成后统一回收所有被标记的对象
- 使用该算法进行垃圾收集会产生以下问题:
复制算法(Copying)
- 将可用内存的容量划分为两块相等的小块
- 每次只使用其中一块,当使用完之后就将还存活的对象复制到另一块未被使用过的内存块中,然后把已使用完毕的这块内存一次清理
- 使用该算法进行垃圾收集会造成大量的内存空间的浪费
标记-整理算法(Mark-Compact)
- 首先标记出所有需要被回收的对象
- 在标记完成之后将存活的对象都向一侧移动
- 然后清理掉存活对象另一侧的内存
分代收集算法(Generational Collection)
- 将Java堆划分为新生代和老年代,针对每个分代的特点使用不同的收集算法
HotSpot算法实现
- 枚举根节点
- 进行可达性分析,枚举所有GC Roots节点
- 可达性分析对执行时间是敏感的,这 一点体现在GC停顿上,在进行分析的时候必须要爆在一个能确保“一致性”的快照中进行(一致性:整个分析期间整个执行系统看起来是被冻结在某个时间点的),否则的话不能保证分析的准确性。
- 因此在可达性分析期间GC会停顿所有正在执行的线程,即“Stop The World”
- 由于使用的是准确式GC,虚拟机通过一组称为OopMap的数据结构来得知那些地方存放着对象引用,并在特定的位置记录下来
- 安全点
- HotSpot不会为每条指令都生成OopMap,只是在“特定的位置”记录下来了这些信息,我们称之为”安全点(Safe Point)“
- 安全点,基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的,包括“方法调用”、“循环跳转”、“异常跳转”等
- Safe Point如何保证所有的线程都在最近的安全点上停顿?
- 抢先式中断(Preemptive Suspension),不需要线程执行的代码来配合,在GC发生时,首先把所有线程都中断,如果发现中断的线程不在Safe Point上,那么就恢复线程让它运行到最近的Safe Point上
- 主动式中断(Voluntary Suspension),当GC要中断线程时,不直接对线程进行操作,仅设置一个标志,各个线程执行时主动去轮询这个标志,当中断标志位真时,就自己中断挂起,轮询标志的地方和安全点是重合的。再加上创建对象需要分配内存的地方
- 安全区域
- 当程序不执行的时候,即线程进入了Sleep、或者Blocked状态,此时线程无法相应JVM的中断请求,就无法自己运行到最近的Safe Point上,这种情况下就需要安全区域来解决
- 安全区域(Safe Region),指在一段代码片段之中,引用关系不会发生变化,在这个区域中的任意位置开始GC都是安全的
- 在线程执行到了Safe Region中的代码时,首先标记自己已经进入了Safe Region中,在这段时间内如果JVM要发生GC,就不用管标识自己为Safe Region状态的线程了,当线程要离开Safe Region时,它会检查系统是否已经完成了根节点枚举,如果完成了那么线程就继续执行,如果没有完成那么线程必须等待知道收到可以离开Safe Region的信号为止
常见的垃圾收集器
- Young Generation(新生代)
- Serial GC
- 单线程收集器
- 会触发Stop The World
- 使用复制算法
- ParNew GC
- Parallel Scavange
- 并行的多线程收集器,吞吐量收集器,使用该收集器进行GC的目的是达到一个可控制的吞吐量
- 吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))
- 涉及参数:-XX:MaxGCPauseMillis(控制最大垃圾收集停顿时间),-XX:GCTimeRatio(直接设置吞吐量大小)
- Tenured Generation(老生代)
- CMS
- 用于服务端的一种并发的收集器
- 使用Mark – Sweep 算法
- 收集过程
- 初始标记:标记GC Roots能直接关联到的对象,会触发STW
- 并发标记:进行GC Roots Tracing,会触发STW
- 重新标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录
- 并发清除:
- 优点:并发收集、低停顿
- 缺点:对CPU资源非常敏感,虽然不会暂停用户线程,但是会导致应用程序变慢,为此JVM提供了一种称为“增量式并发收集器(i-CMS)”的CMS收集器变种(Deprecated);无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而导致另一次Full GC的产生(浮动垃圾:CMS在并发清除阶段用户线程还在运行,此时会不断有新的垃圾产生,这部分垃圾出现在标记过程之后,CMS无法在当次收集中处理掉他们);由于使用的是标记-清除算法,所以会产生大量内存碎片
- Serial Old
- Serial 收集器的老生代版本
- 使用标记-整理算法
- Parallel Old
- Parallel Scanvange收集器的老生代版本
- 使用标记-整理算法
- Both
- G1,是一款面向服务端的垃圾收集器
- 特点:
- 并行与并发,利用多核、多CPU环境下的硬件优势缩短STW的时间
- 分代收集
- 空间整合,G1整体上基于“标记-整理”的算法实现,局部是基于复制算法实现的,这意味着使用G1收集器不会产生内存碎片,收集后能提供规整的可用内存,有利于程序长时间运行
- 可预测的停顿,可以建立可预测的停顿时间的模型
- 使用G1收集器,Java堆的内存布局被分为多个大小相等的独立区域
- G1收集器会跟踪各个Region中垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
- 如何在G1中避免全堆扫描?
- Java虚拟机使用Remembered Set来避免全堆扫描
- G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,都会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中
- 如果是,通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set 中
- G1收集器收集步骤
- 初始标记,标记GC Roots能直接关联到的对象,并且修改了TAMS(Next Top at Mark Start)的值,会触发STW
- 并发标记,从GC Roots开始对堆中的对象进行可达性分析,找出存活对象
- 最终标记,修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录
- 筛选回收,先对Region中按照最有回收价值和成本进行排序,然后根据 用户所期望的GC停顿时间来制定回收计划