JVM垃圾收集器
了解JVM虚拟机是为了具有排查各种内存溢出、内存泄漏问题的能力,在产生性能瓶颈时,我们也需要相关知识对垃圾回收过程进行必要的监控和调节。
对于程序计数器、虚拟机栈、本地方法栈来说,它们的内存分配和回收都具备确定性。而堆和方法区中的内存的分配和回收都是动态的,因此只讨论对于这些区域的垃圾收集。
一、对象死亡的判断
对象死亡即是该对象再也无法由任何途径使用,垃圾收集器只能对已经死亡的对象进行垃圾收集。判断对象死亡的算法主要有两种:引用计数算法和可达性分析算法
1. 引用计数算法(Reference Counting)
- 有一个地方引用,计数器就加1
- 有一个地方引用失效,计数器就减1
- Python中使用了这种算法
缺点:无法解决对象间的。循环引用问题
2. 可达性分析算法(Reachability Analysis)
- 设定一系列“GC Roots”作为起点,若任何一个GC Roots都无法到达某个对象,则该对象被判定为死亡
- GC Roots包括
- 虚拟机栈中引用的对象
- 方法区中类的静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象,
3. 引用类型
JDK1.2之后,Java扩充了引用的概念,将引用类型分为强引用、软引用、弱引用和虚引用
- 强引用的对象无论如何都不会被回收
- 软引用在发生OOM之前会被列入回收范围进行第二次回收,若回收后还是没有足够内存,则就会抛出异常
- 弱引用比软引用更弱一些,不论当前内存是否足够,下次GC的时候一定会被回收
- 虚引用是否存在不会影响一个对象的生存时间。唯一的目的是在对象被GC的时候收到一个系统通知
4. 最终死亡
- 一个对象的最终死亡需要经过两次标记
- 第一次标记:由可达性算法进行分析,若不可达,则进行一次标记
- 第二次标记:判断是否有必要执行finalize()方法,若有必要执行,则暂时不标记
5. 回收方法区
方法区中主要有两个部分需要回收,即:废弃变量和无用的类
- 废弃常量的回收和堆中对象的回收非常类似
- 判断一个类是否需要回收需要满足三个条件
- 该类的所有实例都已经被回收
- 该类的ClassLoader已经被回收
- 该类的java.lang.Class对象在任何地方都没有被引用,无法在任何地方通过反射访问该类的方法
频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证方法区不会溢出
二、垃圾收集算法
1. 标记-清除算法(Mark-Sweep)
- 首先标记需要清除的对象,标记完成后统一进行回收
- 存在两个问题:
- 效率问题
- 内存碎片问题
2. 复制算法(Copying)
- 分为两块,在清除时,先将存活的对象复制到另外一块上面,再对原有的内存空间进行整体清除
- 实际上并不需要1:1地划分两个区域,比如HotSpot虚拟机
- HotSpot中将新生代内存分为较大的Eden空间和两个较小的Survivor空间(8:1)
- 每次回收内存时,将Eden空间和其中一块Survivor空间中还存活的对象一次性复制到另一块Survivor空间中
- 若目标Survivor空间的内存不足,则采用老年代的内存进行分配担保(Handle Promotion)
3. 标记-整理算法(Mark-Compact)
- 标记需要清除的对象
- 所有不需要清除的对象向被标记的对象方向移动
- 最后清除掉边界外的内存
4. 分代收集算法(Generational Collection)
- 根据对象的存活周期将内存分为几个部分
- 一般是分为新生代和老年代,不同代内采用不同的垃圾收集算法
- 新生代中对象存活率低,并且有分配担保,可以采用复制算法
- 老年代中对象存活率高,没有分配担保,必须采用标记-清除或者标记-整理算法
三、HotSpot垃圾收集实现要点
1. 枚举根节点
用于可达性分析,需要"Stop The World",可以采用OopMap进行优化
2. 安全点
只在特定的位置设置了OopMap,有了OopMap才可以GC。这些位置称为安全点(SafePoint)
3. 安全区域
在安全区域(Safe Region)中,引用关系不会发生变化,在其中任何地方开始GC都是安全的。
四、垃圾收集器
1. Serial收集器
- JDK1.3.1之前唯一选择
- 单线程完成收集
- 在收集过程中,必须停止其他线程
- 新生代采用复制算法,老年代采用标记-整理算法
2. ParNew收集器
- Serial收集器的多线程版本
- 只有Serial和ParNew可以和CMS收集器配合使用
3. Parallel Scavenge收集器
- 用于新生代,采用复制算法
- 特点在于可以精准地控制吞吐量(运行用户代码时间/CPU总消耗时间)
- 适用于在后台运算而不需要太多交互的任务
4. Serial Old收集器
- Serial的老年代版本
- 单线程
- 主要用于Client模式
5. Parallel Old收集器
- Parallel Scavenge 的老年代版本
- JDK1.6之后提供
- 与 Parallel Scavenge 配合,更好地控制吞吐量
6. CMS (Concurrent Mark Sweep)收集器
- 以最短回收停顿时间为目标的收集器
- 用于重视服务响应速度的场景上
- 基于标记-清除算法
- 分为四个步骤
- 初始标记,耗时短,不可并发
- 并发标记,耗时长,可以并发
- 重新标记,耗时稍短,不可并发
- 并发清除,耗时长,可以并发
7. G1(Gabage-First)收集器
- JDK1.7中可以正式商用
- 特点有:
- 并行与并发
- 分代收集
- 空间整合
- 可预测的停顿
- JDK9之后成为默认收集器
参考
- 深入理解Java虚拟机