垃圾回收机制GC Garbage Collection
堆是Java虚拟机进行垃圾回收的主要场所,其次要场所是方法区。JAVA8 之后Java将堆内存分为2大部分:新生代(1/3)、老年代(2/3),其中新生代又进一步划分为Eden、S0、S1(Survivor)三个区,Eden,S0,S1比例8:1:1。 Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生。
为什么分区(代)?
1.将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及 GC 频率。
2.针对分类进行不同的垃圾回收算法,对算法扬长避短
GC的种类
Minor GC:从年轻代回收内存 Major GC:清理老年代
Full GC:清理整个堆空间,包括年轻代和老年代
GC触发的条件有两种:
(1)程序调用System.gc时可以触发;
(2)系统自身来决定GC触发的时机
GC过程
1)我们创建的对象会优先在Eden分配,如果是大对象(很长的字符串数组)、长期存活的对象则可以直接进入老年代;
2)当E区填满时,需要回收,回收时先将Eden区存活对象复制到一个survivor0区,然后清空eden区,继续分配,当E区又填满时,则将E区和S0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,如此反复。
3)当对象在 Survivor 区躲过一次Minor GC 后,其年龄就会+1,只有经历15次, 默认情况下年龄到达 15 的对象会被移到老生代中。
4)对象优先在E区中分配,当E区中没有足够空间时,虚拟机将发生一次Minor GC,因为Java大多数对象都是朝生夕灭,所以Minor GC非常频繁,而且速度也很快。当养老区内存不足时,再次触发 Major GC,进行养老区的内存清理,处理完还不足时,会产生Full GC,当执行Full GC后空间仍然不足,则抛出如下错误Java.lang.OutOfMemoryError:
如何判断一个对象是否存活:可达性分析
可达性分析:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可达的,不可用。要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记;
第二次标记:第一次标记后接着会进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。在finalize()方法中没有重新与引用链建立关联关系的,将被进行第二次标记。第二次标记成功的对象将真的会被回收,如果对象在finalize()方法中重新与引用链建立了关联关系,那么将会逃离本次回收
垃圾回收的优先级相当低,垃圾回收器工作,finalize()也不一定得到执行,这是由于程序中的其他线程的优先级远远高于执行finalize()函数线程的优先级。或者说,如果是某个对象在等待清理队列中如果又被调用,则不会执行finallize()
作为GC Roots的对象有以下几种:
- 虚拟机栈中引用的对象、
- 方法区类静态属性引用的对象、
- 方法区常量池引用的对象、
- 本地方法栈JNI引用的对象
垃圾回收算法
1)标记-清除算法:先找到内存里的存活对象并对其进行标记,然后统一把未标记的对象统一的清理;缺点:标记和清除过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片。
2)复制算法:针对新生代。 按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。解决了标记清除法的空间碎片问题,每次清除针对的都是一整块内存,缺点是每次只能用到一半内存。如今都采用复制收集算法来回收新生代,将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。
3)标记整理算法:针对老年代。标记阶段和上面算法相同,但后续步骤不是直接进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
4)分代收集算法:java中使用的就是分代收集算法,存活率低的对象放在一起,称为年轻代,使用复制算法来收集。存活率高的对象放在一起,称为老年代,使用标记-清除或者标记-整理算法
垃圾收集器分类
新生代收集器:Serial ParNew Parallel Scavenge
老年代收集器:Serial Old CMS Parallel Old
堆内存垃圾收集器:G1(Garbage First)
垃圾回收器总结
垃圾回收器 | 线程数 | 方法 | 缺点 | 目的 |
新生代 Serial | 单线程 | 复制算法 | 其他工作线程被暂停 | 虚拟机运行在Client模式下的默认新生代收集器 |
ParNew | 多线程 | 复制算法 | 只有它能与CMS收集器配合工作 | |
Parallel Scavenge | 多线程 | 复制算法 | 追求高吞吐量 | |
老年代Serial old | 单线程 | 标记整理 | ||
Parallel old | 多线程 | 标记整理 | ||
CMS | 多线程 | 标记清除 | 产生内存碎片,对CPU敏感,有浮动垃圾 | 获取最短回收停顿时间 |
G1 | 多线程 | 标记整理 | 内存分为固定大小的区域,有优先级回收 | 实现可预测的停顿时间模型,最高的收集效率 |
1)新生代垃圾收集器Serial : 同一时间段内只允许有一个CPU用于执行垃圾回收操作,回收时,其他工作线程被暂停,只有一个GC线程,直至垃圾收集工作结束。采用复制算法,单线程收集;虚拟机运行在Client模式下的默认新生代收集器。
2)新生代ParNew收集器:Serial收集器的多线程版本,采用复制算法实现,回收时可以多个GC线程;它是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作
3)新生代Parallel Scavenge收集器:复制算法,并行多线程,目的则是达到一个可控制的吞吐量(Throughput,即CPU用于运行用户代码的时间与CPU总消耗时间的比值,虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,吞吐量就是99%。吞吐量优先,高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务
4)老年代Serial Old收集器:Serial收集器的老年代版本,单线程,使用“标记-整理”算法。
5)老年代Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,多线程,使用“标记-整理”算法。在注重吞吐量及CPU资源敏感的场合,都可以优先考虑
6)老年代CMS收集器
基于"标记-清除"算法,一种以获取最短回收停顿时间为目标的收集器,停顿时间越短对于需要与用户交互的程序来说越好,良好的响应速度能提升用户的体验;多线程回收器,采用标记-清除算法,低停顿。
CMS收集器的缺点:
1)对CPU资源非常敏感,面向并发设计的程序都会对CPU资源较敏感,CMS默认的回收线程数为:开启的线程数为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对 用户查询 的影响将会很大,因为他们要分出一半的运算能力去 执行回收器线程;
2)无法处理浮动垃圾
3)采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。
CMS步骤:
1)初始标记: 暂停所有线程, 可达性分析,标记一下GC Roots能直接关联到的对象,速度很快;
2)并发标记:并发标记的时候 GC 线程和用户线程是同时存在的,这个过程中会记录所有可达的对象,但是这个过程结束过后由于用户线程一直在运行所以还会产生新的引用更新,并不能保证可以标记出所有的存活对象;
3)重新标记 : 为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分,暂停用户线程,采用多线程并行;停顿时间:初始标记<重新标记<<并发标记
4)并发清除: 清理的时候用户线程是可以继续运行的,GC 线程只清理标记的区域。
7)G1 垃圾收集器Garbage First
范围是整个堆内存,把整个堆内存划分为多个大小相等的独立区域,实现可预测的停顿时间模型,之前的收集器进行收集的范围都是整个新生代或老年代,而G1将整个Java堆(包括新生代、老年代)划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。区域划分、有优先级的区域回收,保证了G1收集器在有限的时间内可以获得最高的收集效率
特点:1)保留着新生代和老年代的概念,它们分别都是一部分 Region,自动设置每个区域的大小
2)从整体看,是基于标记-整理算法;从局部(两个Region间)看,是基于复制算法,结合多种垃圾收集算法,空间整合,不产生碎片
3)可预测的停顿:建立可预测的停顿时间模型 ,可以明确指定M毫秒时间片内,垃圾收集消耗的时间不超过N毫秒;
应用场景
面向服务端应用,针对具有大内存、多处理器的机器; 最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案; jdk9 默认使用 G1 收集器。
过程如下
① 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。
② 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
③ 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。
④ 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划,这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。