垃圾回收器及算法基础
前提介绍
垃圾回收器(GC)的理论研究在很早直接就开始了,大概是在60年代lisp语言就开始使用内存分配和垃圾回收机制来管理程序的内存了,近现代这一方向也是保持着极高的研究热度,从G1->ZGC
算法基础
对内存空间的回收首先需要解决一个问题那些是需要进行回收的对象,针对这个问题有两种处理方案:
-
引用计数算法
引用计数是通过对对象被引用次数来标识是否有效,存在循环引用问题,可以通过可达性分析的步骤来解决 -
根标记算法,从GC Roots开始遍历整个内存对象是否可达的方式来标记需要回收的对象
首先GC Roots对象包括以下的几种:
- 虚拟机栈中引用指向的对象(reference)
- 方法区中类静态属性引用的对象(静态属性对象,虽然不在栈帧中)
- 方法区常量引用的对象(例如常量池中的对象)
- 本地方法栈中引用的对象
对象
-
对象的回收
对象的回收是一个非常严肃的动作,错误的回收将会是一场对用户的灾难,因此在JVM的实现中,对于GC线程的优先级设置的非常低,其次是在标记对象不可达后,还会声明会执行一次对象的**finalize()**方法,对于这个方法是否执行完成不做保证(防止死循环/wait time)
- finalize():用于GC回收前对象最后一次自救的方法,但是只会执行一次,防止内存溢出
-
对象的分配
对象的分配与垃圾收集器的选择有关,对象是优先在Eden区进行分配,
进入老年代的几种场景:
- 大对象直接进入老年代
- 长期存活的对象(16次GC)
- 动态对象年龄判断(一半相同年龄的对象直接进入老年代)
理论基础
-
标记类
标记-清除算法/标记-整理算法/复制算法
- 标记整理算法是通过时间换空间形式的一种体现,通过整理回收后的内存碎片来达到可以继续分配的目的
- 复制算法是空间换时间的体现,通过预留一倍的空间来达到内存回收完成后没有内存碎片的问题
-
分代类
分代算法主要是根据对象的存活周期,将对象划分为不同区域,在不同的区域上采取不同的算法来处理
HotSopt实现
-
枚举根节点
通过OopMap的的方式来快速查找GC Roots枚举
-
安全点
由于引用关系的变化会不断的影响GC Roots,因此HotSpot不是每条指令都会生成OopMap,而只在特定的位置才会产生OopMap,这种特殊的位置被称为Safe pint,有两种进入Safe point的方式:
- 抢占式
- 主动中断式:主动中断的方式是通过设置中断标记
-
安全区域
安全区域被称为Safe Region,指的是一段不会改变引用关系的代码,在执行GC时是不会处理已进入Safe Region的线程,当这些线程在离开Sefe Region时也会检查GC是否完成
垃圾收集器
-
Parallel Scavenge收集器
-
Parallel Old收集器
两种Parallel收集器分别是jdk8中默认是年轻代和老年代的垃圾收集器
-
Parallel Scavenge收集器
用于新生代,关注吞吐量的多线程并行收集器
CMS是关注于停顿时间,尽可能的缩短用户线程的停顿时间,适合于有交互的web系统
Parallel是关注于吞吐量尽可能的在单位时间内多执行用户代码,缩小GC时间,适用于后台任务
-
Parallel Old收集器
是老年代的标记-整理算法的多线程垃圾收集器
-
CMS收集器(三标一清)
CMS收集器是以最短停顿时间为目标的垃圾收集器,是基于标记-清除算法演化而来,分为:
- 初始标记阶段
- 并发标记阶段
- 重新标记阶段
- 并发清除阶段
在初始标记和重新标记阶段会STW的方式来进行,因为初始化标记是查找**GC Roots,**重新标记是对并发标记阶段结果修正也需要STW;
整个CMS中最耗时的并发标记和并发清理阶段都可以和用户线程一起执行
CMS垃圾收集器的问题:
- 参与线程过高,按照(coreSize+3)/4的方式分配
- 无法处理浮动垃圾,对于在重新标记后产生的垃圾只能在下一次GC时进行处理,占用空间,当老年代达到一个阈值后会出发full GC,当CMS在运行时产生内存不足会激活serial GC
- 内存碎片问题,通过full GC的方式解决
-
G1收集器
- 从jdk9开始G1就作为默认的垃圾处理器
- 在整体上G1是标记-整理算法,局部上是标记-复制算法
-
Shenandoah/ZGC收集器
- 亚毫秒级
- 无分区设计
-