JVM垃圾回收器 快速入门
- 判断可以被回收的对象算法
-
引用计数法
在对象中添加一个引用计数器,当对象被引用时,计数器加一,当引用失效时,计数器减一。计数器为0的对象可以被回收。引用计数法的缺陷是无法处理循环引用的情况,而循环引用在Java中非常常见,因此Java中的垃圾回收器一般不使用引用计数法。
-
可达性分析法
通过一系列GC Roots对象,向下遍历,标记所有能够被GC Roots引用到的对象,而未被引用的对象就被认为是垃圾对象并被回收。
-
GC Roots对象包括以下几种:
-
System Class:系统类
-
Native Stack:本地方法栈中JNI(即Java Native Interface)中引用的对象
-
Thread:活动线程的虚拟机栈(栈帧中的本地变量表)中的引用对象
-
Busy Monitor:正被加锁的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
-
四种引用
- 强引用:最常见的引用类型,如果没有明确指定其他类型的引用,那么默认使用的就是强引用。强引用适用于 Java 中的任何对象,包括字符串、数组、自定义类等等。只要强引用存在,该对象就不会被垃圾回收,因为强引用本身就是GC Roots。
-
String str = "Hello World!";
在这个示例中,str
变量是一个对象引用,它指向一个字符串对象,这个字符串对象是通过字符串字面量创建的。这是一个强引用,因为它是默认的引用类型,它将在引用它的变量离开作用域或被显式地赋值为 null
时才会被垃圾回收器回收。
- 软引用:软引用指向的对象不会被作为GC Roots,如果对象只有软引用指向,没有强引用指向,就会被回收。
可以通过SoftReference
类来创建软引用。
SoftReference<String> softRef = new SoftReference<>("str");
String str = softRef.get();
- 弱引用:弱引用指向的对象不会被作为GC Roots,当垃圾回收器扫描到只有弱引用指向的对象时,无论内存是否充足,无论是否有其他引用指向,都会回收该对象。
可以通过WeakReference
类来创建软引用。
WeakReference<String> weakRef = new WeakReference<>("str");
String str = weakRef.get();
- 虚引用:虚引用是所有引用类型中最弱的一种,它不能单独使用,必须和引用队列一起使用,主要配合ByteBuffer使用。当对象被虚引用所引用时,垃圾回收器会在下一次回收阶段将对象销毁并且在销毁对象之前,将该对象对应的虚引用对象放入相应的引用队列中,以便程序通过轮询引用队列获取被虚引用对象销毁的消息。
- 终结器引用:一种虚引用的子类,用于跟踪对象是否被终止(Finalized)。当对象被垃圾回收器回收时,如果它的终结器方法还没有被调用过,那么就会将终结器引用添加到一个全局的引用队列中,等待 Finalizer 线程来调用对象的终结器方法。由于 Finalizer 线程的调度是由 JVM 自行管理的,因此终结器方法的调用时间是不确定的,可能会影响程序的性能和可靠性。
终结器方法
finalize()
:在 Java 中,每个对象都有一个终结器方法finalize()
,在对象被垃圾回收器回收之前,该方法会被调用一次,主要用于在对象被销毁之前执行一些清理操作,例如关闭文件、释放资源等。但是,终结器方法存在一些问题,例如无法保证执行时间、无法避免死锁等,因此在 Java 9 后推荐使用Clean API。终结器引用用于管理这些终结器。
- 垃圾回收算法
-
标记-清除算法:先标记所有存活的对象,然后删除所有未标记的对象以及它们的引用,最终回收被删除对象所占用的内存空间。
缺点:无法避免内存碎片;标记和清除的过程会影响应用程序的性能。
-
标记-整理算法:先标记所有存活对象,然后将它们向内存的一端压缩,清除其他未标记的对象和它们的引用,最终回收被删除对象所占用的内存空间。
优点:可以避免内存碎片
缺点:需要移动存活对象的位置,影响应用程序的性能。
-
复制算法:将现有的存活对象复制到另一片内存区域,然后清除旧的内存区域中所有未被复制的对象,最终得到一片连续的内存空间。
优点:可以避免内存碎片
缺点:需要额外的内存空间来存储复制对象,也会增加GC的时间开销。
-
分代回收算法:基于分代假设(即一个对象的生命周期分为不同的阶段,不同阶段的对象具有不同的存活概率)将内存分为两个或多个空间,通常称为新生代和老年代。新生代中的对象生命周期短暂,因此使用复制算法;老年代中的对象生命周期较长,因此使用标记-压缩算法。
新生代:新生代通常由三部分组成,一部分是伊甸园(Eden Space),其余两部分是幸存区(Survivor Space)。
老年代:老年代用来存储生命周期较长的对象,这些对象是从新生代中来的。由于老年代中的对象生命周期比较长,采用标记-清除或者标记-整理算法来进行垃圾回收。在执行垃圾回收时,大部分的老年代空间是无需扫描的,只会扫描其中存在的一部分活动对象和可能存在的指向年轻代的引用。
-
一开始,Eden Space 是空的,随着新对象创建,对象被存储在 Eden Space 中。
-
当第一次 Eden Space 被填满时,会触发一次 Minor GC。在 Minor GC 中,会检查 Eden Space 中所有的存活对象,并将其通过复制算法转移到幸存区中的一块空闲区域中。
-
随后的 Eden Space 内存不足,Eden Space 和from幸存区存活的对象通过复制算法转移到to幸存区。
-
当新生代中的对象经历多次GC仍然存货时,就会进入老年代。
-
如果在minor gc时,新生代内存不足以容纳幸存的对象,且两个幸存区也无法容纳所有幸存的对象,则这些对象就会被晋升到老年代中。
-
当老年代空间不足时,会先尝试触发Minor GC;当无法再通过 Minor GC 释放出足够的内存空间时,会触发Full GC。
-
- 垃圾回收类型
-
Full GC:主要回收老年代中的无用对象。老年代中的对象生命周期比较长,因此Full GC的频率相对较低,但是Full GC会暂停所有应用线程(Stop The World,STW),并且需要更多的时间来完成,因此会对系统的性能产生一定的影响。在Full GC过程中,虚拟机会对整个堆进行回收,包括新生代和老年代,以便回收更多的内存空间。
Full GC的触发方式:
-
当新生代和老年代的内存均占满时,会触发 Full GC 进行全面垃圾回收。
-
当CMS GC 无法处理内存碎片并达到了指定的 GC 触发条件时,会触发 Full GC 进行清理。
-
当 Perm 区空间不足以容纳新的对象时,JVM 会触发 Full GC 来回收 Perm 区中的垃圾以释放内存。
-
调用 System.gc() 方法时,会强制触发 Full GC 进行一次全面垃圾回收。
-
-
Minor GC:主要回收新生代中的无用对象,由于新生代对象生命周期短暂,因此Minor GC的频率比较高,一般不会影响系统的性能。在Minor GC过程中,虚拟机会短暂暂停所有应用线程(Stop The World,STW),以便回收内存空间。
-
Mixed GC:是一种介于Minor GC和Full GC之间的垃圾回收方式。在JDK 8之后的G1垃圾回收器中,引入了Mixed GC。
-
System GC:系统垃圾回收,是一种由系统自动触发的垃圾回收方式。System GC通常是由应用程序通过调用System.gc()方法请求系统执行垃圾回收操作。调用System.gc()方法请求执行的System GC通常会触发Full GC,也就是对整个堆内存进行垃圾回收。
- 垃圾回收器
-
常见分类:
-
串行
-
最简单的垃圾回收器
-
使用单线程进行垃圾回收:进行垃圾回收的时候会触发STW直到收集完成
-
适用于小型应用程序
-
简单且轻量级
-
组合:Serial + Serial Old
-
-
吞吐量优先
-
旨在最大化垃圾回收的吞吐量
-
使用多线程进行垃圾回收
-
适用于注重系统吞吐量的应用程序,如后端数据处理、批处理等
-
适用于堆内存较大的场景
-
让单位时间内,STW时间最短
-
组合:Parallel Scavenge + Parallel Old
-
-
响应时间优先
-
旨在最小化垃圾回收的停顿时间
-
使用多线程进行垃圾回收
-
适用于对响应时间有严格要求的应用程序,如Web应用程序和实时系统
-
适用于堆内存较大的场景
-
让单次STW时间最短
-
-
-
常见的JVM垃圾回收器:
-
Serial收集器:这是JVM的一个基本垃圾回收器,它使用单线程进行垃圾回收。Serial收集器适用于小型应用程序,它的简单性和轻量级的特点使其在客户端应用程序中非常流行。
-
Parallel收集器(并行):Parallel收集器与Serial收集器类似,但使用多线程进行垃圾回收。Parallel收集器适用于大型应用程序,其并发能力可以提高应用程序的响应速度。
-
CMS收集器(并发):CMS(Concurrent Mark Sweep)收集器是一种低暂停时间的垃圾回收器,它使用多线程并发进行垃圾回收。它适用于对应用程序响应时间有严格要求的场景,例如Web应用程序。CMS使用标记-清除算法,分为以下4个阶段:
-
初始标记:标记一下GC Roots能直接关联到的对象,会STW。
-
并发标记:GC Roots Tracing,可以和用户线程并发执行。
-
重新标记:标记期间产生的对象存活的再次判断,修正对这些对象的标记,执行时间相对并发标记短,会STW。
-
并发清除:清除对象,可以和用户线程并发执行。
-
-
G1收集器:G1垃圾回收器是一种基于区域的垃圾回收器,它采用了分区(Region)的思路,将Java堆内存分成多个大小相等的区域(Region),每个区域的大小通常为1MB或更大。G1垃圾回收器通过计算每个区域的回收收益率来确定哪些区域应该被优先回收。
回收收益率是指回收一个区域所能够释放的内存量与该区域当前使用的内存量的比率。G1垃圾回收器通过以下步骤计算每个区域的回收收益率:
-
跟踪每个区域的对象分配和回收情况计算每个区域的垃圾占比。
-
在垃圾收集过程中标记每个区域中的存活对象计算每个区域的存活对象占比。
-
通过将每个区域的垃圾占比和存活对象占比相乘,计算每个区域的回收收益率。
在计算每个区域的回收收益率后,G1垃圾回收器会根据回收收益率的大小来确定哪些区域应该被优先回收,即优先回收回收收益率高的区域,以最大限度地释放可用内存。
-
-
-
默认垃圾回收器
-
Java 8 及之前:串行垃圾回收器
-
Java 9 及之后:G1垃圾回收器
-
-
CMS 和 G1 的区别:
特性 | CMS | G1 |
---|---|---|
回收算法 | 标记-清除 | 复合式垃圾回收算法 |
碎片问题 | 产生大量碎片 | 划分Java堆为多个大小相等的区域,减少碎片 |
暂停时间 | 需要停止应用程序的执行,暂停时间长 | 使用并发线程进行垃圾回收,并允许将垃圾回收任务分散执行,暂停时间短 |
额外空间 | 需要额外空间保留垃圾对象 | 可以使用一部分Java堆进行垃圾回收,不需要额外空间 |
适用场景 | 对停顿时间敏感的应用程序 | 需要大堆和低延迟的应用程序,可以最小化暂停时间并在垃圾回收时减少内存碎片 |