【JVM垃圾回收(GC)机制(一文读懂)】

背景

众所周知,Java语言是依托于Java Virtual Machine (JVM)进行工作的,JVM 又包括几大部分类加载子系统、运行时数据区、执行引擎、本地方法库等四大部分。其中垃圾回收就是发生在运行时数据区,篇幅有限,本次只分析运行时数据区

垃圾收集相关概念

安全点/安全区

在进行 STW 的之前,JVM要保证线程达到最近的安全点/安全区才会进行。安全点/安全区可以理解为:在这个点或者区域内,进行STW不会对对象的引用关系发生变化

内存泄露和内存溢出

内存溢出

OOM : 没有空闲的内存,并且垃圾回收也无法再回收足够的空间。

内存泄漏

只要对象不再被程序使用,并且 GC 又不能回收该对象。
比如一些不手动关闭的数据库连接、网络套接字连接等

垃圾回收的并行与并发

并行(Parallel):是指垃圾回收线程可以在某一个时间点同时执行多个。

如:Parallel New 、Parallel Scavenge、Parallel Old

并发(Concurrent):是指在某个时间段内垃圾回收线程和用户线程能够同时进行。但并不一定是并行,有可能是交替执行。

但是同样还是会有 STW 事件。

运行时数据区

见明知意,运行时数据区就是提供JVM在运行过程的数据存储场所。其中主要分为几大部分:元空间(方法区)、堆空间、虚拟机栈、程序计数器、本地方法栈五大部分。以下为五大部分存储的数据类型:

  • 元空间:存储类模板(Class模板)、反射(代理)产生的类Class对象、类对象的运行时常量池、即时编译后产生的代码缓存(JIT技术)
  • 堆空间:代码new的对象,字符串常量池
  • 虚拟机栈:Java代码执行过程中的最小单位。本地变量表、操作数栈、符号引用、方法返回、附加信息
  • 程序计数器:一个线程执行程序整体的顺序,首先。。。其次。。。
  • 本地方法库:Java需要依赖的非Java语言编写的方法,常见的是以native修饰的。

针对以上几大部分,这里只着重介绍堆空间和元空间。因为GC基本上回收的数据都是从这两部分回收的。

元空间

在 JDK 1.8 的时候改为了元空间,在 JDK1.8之前叫法为方法区。主要存储的是大Class对象的模版信息、运行时由代理或者cglib 产生的大Class对象、运行时常量池
运行时常量池是指在编译期已经确定了的常量数据(基本类型、方法引用、属性引用、类名信息)等
运行时常量池

堆空间

是存储用户声明的运行时对象,包括但不限于使用 new 关键字、反射等方法声明。其中针对GC来说,堆空间又被分为新生代老年代

新生代

存放新生成的对象,主要分为 Eden区、Survivor0区、Survivor1区,内存空间比例默认为 8 : 1 : 1,可以使用配置修改。-XX:NewRadio=xxx
新生代的垃圾回收过程称之为 Young GC(Minor GC)

Eden区

主要存放的是未被垃圾回收,刚新鲜出炉的新对象。
在垃圾回收的过程中,Eden区被标记为需要回收的对象时,就会被清空,幸存下来的对象就会和幸存者0区,放在幸存者1区(幸存者0区/1区并不是固定的,又称之为From区和To区,From就是存在存活对象的空间,To区就是没有存放任何对象的空间),注意当幸存者0区/1区的大小无法承接存活的对象时,就会直接放入老年代,以满足对象存储需要

Survivor 0区/1区

二者其实没有明显的区别,内存大小一致,存储对象一致,只是在使用的过程中,可能会代表的意义动态变化。如果本次 Survivor 0区代表GC后存储的对象空间,那么Survivor 0 就是 To区,Survivor 1 就是From区。反之则亦然。

老年代

存放的是经过一定GC次数后仍存活的对象,对象的GC存活次数是存储在对象头中。对象头中还存放了额外的其他数据(对象锁、HashCode、类模板等),本次就不详细说明了。
老年代与年轻代的比例默认为 1:2
老年代的垃圾回收过称之为 Major GC,如果 GC 之后的存储空间仍然不满足新建对象分配空间要求,那么就会触发一次 Full GC,还是不够的话,就会抛出 OOM Error。

垃圾回收算法

在说垃圾回收算法之前,要先说一个概念,叫分代收集。就是根据对象所属内存空间不同,使用不同的垃圾回收算法。因为不同空间的对象GC前后的数量是有差异的。

前置算法背景

标记算法

1、引用计数法。在对象头中维护一个被引用对象列表,在标记阶段通过判断这个列表是否为空,来决定是否需要回收。这种方法过度依赖维护列表的过程,同时无法避免循环引用的问题,如果两个对象的列表互相引用,那么在JVM终止之前都不会被回收,即时已经没有实际使用了,所以Java放弃了该方法
2、可达性标记。JVM维护了一组 GC Roots 对象,从这组对象出发,只要与这组对象存在引用关系的关联对象,都会被标记为存活。GC Roots 对象的来源:元空间的类模板静态属性、虚拟机栈中引用的对象、元空间运行时常量池常量

标记-清除算法

先标记,后清除。在标记过程中要保证对象的不可变性,此时就需要短暂的 STW(Stop The World),如果不保证的话,有可能存在标记前后对对象的回收判断有异常。这种方法会产生内存碎片,对象的生成在逻辑地址上不一定是连续的,如果清空了,就会出现断断续续的小空间,这种会在声明对象时产生额外消耗。

复制算法

在先标记,后清除的基础上,对GC后的对象进行了重新整理排序。首先会申请一模一样的一份内存空间,在清除之后,重新将存活的对象整理顺序排放在新申请的内存空间中,这样就不会存在内存碎片。但需要更换对象的内存地址(虚拟机栈中、运行时常量池中),同时还需要额外的内存开销

标记-压缩算法

先标记,后清除,再压缩。对清除完毕后的对象,进行重新排序压缩。将原本不连续的对象,重新压缩为连续的。优点是解决了内存碎片问题,同时还不需要额外的内存空间,但缺点是压缩需要额外耗时

三种算法的对比

Mark-SweepMark-CompactCopying
速度中等最慢最快
空间开销少(会堆积碎片)少(不会堆积碎片)会耗费两倍的同等空间(不堆积碎片)
是否移动对象

垃圾回收器

由垃圾回收算法的落地实现,称之为垃圾回收器。按照不同的类型划分,可以分为以下几种

  • 按照并行划分:串性垃圾回收器、并行垃圾回收器

串行垃圾回收器

Serial

串行新生代垃圾回收器

年轻代:采用的是标记-复制算法,STW 机制
命令行使用:-XX:+UseSerialGC

Serial Old

串行老年代垃圾回收器

老年代:采用的是标记-压缩算法。可以避免内存碎片的问题
同样采用的是串行回收,STW 机制

Parallel Scavenge

并行清除垃圾回收器,适用于新生代。旨在提高吞吐量而生

只适配于新生代。采用的是复制算法,并行回收。STW 机制
与 ParNew收集器不同的是

  • 目标是达到可控制的吞吐量,也称为吞吐量优先的垃圾回收器。
  • 自适应的调节策略

与之适配的老年代就是 Parallel Old
命令行参数:-XX:+UseParallelGC / -XX:+UseParallelOldGC 作用是一样的,年轻代使用 PS ,老年代使用 PO

ParNew

并行新生代垃圾回收器

多个垃圾回收线程可以同时工作
针对的是年轻代,采用的是标记-复制算法,STW 机制
老年代结合:Serial Old 或者 CMS 命令行使用:-XX:+UseParNewGC 表示新生代使用并行回收,但是不影响老年代
修改并行的线程数:-XX:+ParallelGCThreads ,默认与 CPU 线程数相同

Parallel Old

并行老年代垃圾回收器

Concurrent Mark Sweep

并发标记清除垃圾回收器,适用于老年代。旨在降低停顿时间而生
垃圾回收器结合
为什么 PS 和 CMS 不能同时结合使用?
1、目标不同:
Parallel Scavenge主要关注吞吐量,通过并行处理最大化应用程序执行时间。
CMS主要关注停顿时间,通过并发处理最小化垃圾收集对应用程序的影响。

2、调优冲突:
Parallel Scavenge有自适应调谐功能,它会动态调整新生代和老年代的大小以及其他参数,以达到预定的性能目标。
CMS则要求用户手动调整参数来优化性能,因为CMS需要较精细的调优以确保低停顿时间。

3、实现机制不同:
Parallel Scavenge的新生代GC采用的是并行复制算法(ParNew),而CMS的老年代GC是并发标记-清除算法。这两种算法在内存管理和垃圾收集上有很大的不同,导致它们难以协调工作。
新生代和老年代GC必须紧密配合,以确保垃圾收集的整体效率和内存管理的连贯性。
Parallel Scavenge和CMS的设计初衷和机制决定了它们难以有效配合。

垃圾回收器对比

评估垃圾回收器的指标

  • 吞吐量:运行用户代码的时间占总运行时间的比例 - 总运行时间=程序运行时间+垃圾收集时间
  • 暂停时间:执行垃圾收集的时候,程序的工作线程暂停的时间
  • 内存占用:Java堆区所占的内存大小

高吞吐量和低暂停时间是一种相互竞争的目标关系,这种就像 Parallel Scavenge 和 CMS 不能组合使用一样,二则的优化目标都不同,如果组合使用,会导致更差的效果

总结

垃圾回收器的具体使用场景还是需要与业务场景相结合,综合考虑使用哪种垃圾回收器会更好。但 Oracle 官方已经陆续推出了停顿时间和吞吐量都兼优的 G1 和 ZGC,这两个后续分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值