1. 介绍
1.1 为什么需要垃圾收集器
堆中存放着正在运行的JAVA程序所创建的所有对象,例如使用new指令创建对象。但是没有明确代码来释放它们,垃圾收集就是自动释放不再被程序使用的对象的过程。
1.2 如何判断垃圾对象
- 引用计数法
缺点:循环引用问题
- 可达性分析法
GCroot结点开始向下搜索,路径称为引用链,当对象没有任何一条引用链链接的时候,就认为这个对象是垃圾,并进行回收。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
目前主流JVM采用的垃圾判定算法就是可达性分析法。JAVA代码方法中并不是所有创建的对象都是在栈的本地变量表中的,例如创建一个Person局部变量,其中给这个Person对象再赋值baby对象,则堆中的指向关系为下图的⑥,并不是栈去记录的。
1.4 GCroot对象
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
- 所有被同步锁(synchronized关键字)持有的对象。
- 反映Java虚拟机内部情况的JM XBean、JVM TI中注册的回调、本地代码缓存等
1.5 垃圾收集算法
- 标记-清除
标记对象,然后清理
缺点:内存碎片多
- 复制
区分Eden、Survivor From、Survivor To三个区域,每次将Eden区、Survivor From/Survivor To区复制幸存对象到另一个幸存区,然后直接清理垃圾对象。Yong GC采用这种模式,原因是新生代对象生成频繁,大多对象生命周期短,需要频繁回收垃圾对象,而复制算法是效率最快的,相对于另外两种,但是会浪费一点空间,默认的比例是Eden:Survivor From:Survivor To=8:1:1 。
PS:内存地址会变动,但是对象的hashCode不会变动
- 标记-整理
标记对象,然后将幸存对象移动到内存的一端,然后清除垃圾对象。老年代Full GC会使用这种方式,内存利用率高。
1.6 垃圾收集器分类
对于Java7引入的G1,以及Java11引入的ZGC和Java12引入的Shenandoah GC,这三者都是作用于整个堆的回收器(G1相比后两者有点不同,G1严格意义上也是区分新生代和老年代的)。Yong GC核心算法就是复制, Full GC核心算法就是标记-整理。
- Serial
单线程,慢,客户端模式默认年轻代垃圾收集方式(目前JAVA HotSpot 默认都是服务端模式)。
- ParNew
多线程,默认开启CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数
- Parallel Scavenge
多线程,可控制的吞吐量(吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)),服务端模式默认首选。收集器还有一个参数-XX:+UseAdaptiveSizePolicy值得关注。这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略(GC Ergonomics)。如果读者对于收集器运作原来不太了解,手工优化存在困难的时候,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成将是一个不错的选择。只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用MaxGCPauseMillis参数(更关注最大停顿时间)或GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。
- CMS(Concurrent Mark Sweep)
多线程,标记清除算法
- Serial Old
单线程,慢
- Paraller Old
多线程,标记整理,jvm运行Server模式默认收集器
- G1
G1其实是Garbage First的意思,垃圾优先? 不是,是优先处理那些垃圾多的内存块的意思。去除内存碎片问题,同时保留CMS垃圾收集器低暂停时间的优化。jdk1.7推出,jdk9默认收集方式,代替CMS。G1垃圾收集器相对比其他收集器而言,大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域(分治)。
G1回收步骤:
(1) 初始标记
STW,标记GC ROOTS无法关联到的对象。用时很短。
与用户线程同时执行,将初始标记中所获取到的GC ROOTS对象进行追踪,找到需要回收的对象和不需要回收的对象。用时最长。
STW,这一步是为了标记到在并发标记中,用户线程所产生的新的可回收对象。用时较短。
与用户线程同时执行,通过对区域的回收价值列表以及预测回收时间的综合分析,对可回收的区域进行筛选并回收掉,采用的是复制算法。用时为用户掉预测回收时间。这里的耗时是用户设置的最大垃圾回收时间,可通过-XX:MaxGCPauseMillis=parseMIlls
来设置。
1.7 不同JDK版本垃圾收集器的区别
- jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
- jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
- jdk1.9 默认垃圾收集器G1
2. FAQ
2.1 复制算法和标记整理算法有什么区别?
这两类都分两个步骤:移动对象和清除数据。清除数据两者效率应该都差不多。移动对象的话整理要比复制的多(对象的复制次数)
2.2 三色标记法?
3. 参考资料
【垃圾收集器原理】