目录
3.5 终结器引用(Finalizer Reference) (扩展
JVM(Java虚拟机)的垃圾回收(Garbage Collection)是自动管理Java程序中的内存分配和释放的过程。它通过检测和回收不再使用的对象,从而释放内存并避免内存泄漏。在JVM中使用的垃圾回收算法为可达性分析算法。
如何判断垃圾是否可以回收
1 引用计数算法
引用计数算法是一种简单的垃圾回收算法,它为每个对象维护一个引用计数器,记录当前有多少个引用指向该对象。当引用计数器减为零时,即没有任何引用指向该对象时,该对象被认为是不可达的,可以被回收。
-
在对象中添加一个整型的引用计数器字段,用于记录对象当前被引用的次数。
-
当一个对象被创建时,引用计数器初始化为1。
-
当对象被其他对象引用时,引用计数器加1。
-
当对象的引用失效时,引用计数器减1。
-
在每次引用计数器减1操作后,检查引用计数器的值。如果引用计数器为0,说明当前没有任何引用指向该对象,可以认为该对象是不可达的,可以被回收。
引用计数算法的优点是实现简单,回收对象的延迟时间短。但它无法 解决循环引用的问题,即当两个或多个对象相互引用时,它们的引用计数器永远不会减为零,导致内存泄漏。
2 可达性分析算法
现代虚拟机基本都是采用可达性分析算法来判断对象是否存活,可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点。这样通过 GC Root 串成的一条线就叫引用链,直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为垃圾对象,会被 GC 回收。
2.1 根对象的选择
-
线程栈帧中的局部变量和参数:每个线程在执行过程中,会有一个对应的栈帧,栈帧中包含了局部变量和参数。这些局部变量和参数中引用的对象可以作为根对象。
-
静态变量:静态变量属于类而不是对象,它们在类加载时被初始化,一直存在于内存中。静态变量引用的对象可以作为根对象。
-
JNI(Java Native Interface)引用:JNI允许Java代码调用本地(非Java)代码,本地代码可能会持有Java对象的引用,这些引用可以作为根对象。
-
JVM内部的引用:JVM内部可能会持有一些特殊的引用,例如常量池中的引用、类加载器中的引用等,这些引用也可以作为根对象。
3 Java中的五种引用
3.1 强引用(Strong Reference)
强引用是默认的引用类型。当一个对象具有强引用时,垃圾回收器无法回收该对象,即使内存不足时也不会被回收。强引用是最常见的引用类型,例如通过new
关键字创建的对象,它们都具有强引用。只有当强引用变为不可达时,对象才会被垃圾回收。
强引用是默认的引用类型,可以直接通过变量来设置。
Object obj = new Object(); // 设置强引用
3.2 软引用(Soft Reference)
软引用用于描述一些有用但非必需的对象。当内存不足时,垃圾回收器会尝试回收被软引用引用的对象。当内存回收完毕时,仍存在内存不足,此时被软引用的对象将会被回收。
可以使用SoftReference
类来创建软引用,并通过get()
方法获取引用的对象。
Object obj = new Object();
SoftReference<Object> softRef = new SoftReference<>(obj); // 设置软引用
Object softRefObj = softRef.get(); // 获取引用的对象
3.3 弱引用(Weak Reference)
弱引用比软引用更弱。当垃圾回收器运行时,无论内存是否充足,弱引用都会被回收。
可以使用WeakReference
类来创建弱引用,并通过get()
方法获取引用的对象。
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj); // 设置弱引用
Object weakRefObj = weakRef.get(); // 获取引用的对象
3.4 虚引用(Phantom Reference)
虚引用是最弱的引用类型。一个对象被虚引用引用时,几乎等同于没有被引用。虚引用的主要作用是跟踪对象被垃圾回收的状态。虚引用必须与引用队列(Reference Queue)一起使用,通过监视队列中的引用来了解对象何时被垃圾回收。虚引用常用于管理堆外内存或执行一些特定的清理操作。
可以使用PhantomReference
类来创建虚引用,并通过引用队列(Reference Queue)来接收回收通知。
Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue); // 设置虚引用
3.5 终结器引用(Finalizer Reference) (扩展
终结器引用是一种特殊类型的引用,用于与对象的终结器(Finalizer)相关联。对象的终结器是在垃圾回收器回收对象之前调用的方法,它通常用于执行一些资源清理或释放操作。终结器引用的作用是在对象被回收之前将其注册到终结器队列(Finalizer Queue),然后由专门的终结器线程来执行对象的终结器。
4 引用对列
引用队列(Reference Queue)是Java中的一种特殊数据结构,用于存储被垃圾回收器回收的对象的引用。当一个对象的引用被放入引用队列时,可以通过检查引用队列来得知该对象已经被回收。当对象被放入引用队列时,JVM对记录这个对象的回收状态,引用队列并不会影响JVM对对象的回收,只是起了记录状态的作用。
强、弱、软引用的对象类型可添加引用队列也可以不添加,而虚引用对象必须与引用对列一起使用。
垃圾回收算法
1 标记清除算法
1.1 标记阶段(Mark Phase)
从根对象(如全局变量、活动线程的栈等)开始,递归地遍历对象图,标记所有与根对象可达的对象。标记的方式可以是在对象的标记位上设置一个标记,表示该对象是可达的。
1.2 清除阶段(Sweep Phase):
- 遍历整个堆内存,对每个对象进行判断:
- 如果对象已被标记为可达,则保留该对象,继续遍历下一个对象;
- 如果对象未被标记为可达,则将其回收,释放其占用的内存。
1.3 优缺点
优点:
- 灵活性:标记清除算法可以处理任意的对象图结构,不受对象之间的引用关系复杂性的限制。
- 不需要连续内存:标记清除算法不需要保证回收后的内存空间是连续的,因此可以处理不连续的内存分配情况。
缺点:
- 内存碎片化:标记清除算法在回收对象后,会产生内存碎片(即不连续的空闲内存块)。这会导致后续的内存分配效率降低,可能需要更频繁地进行内存分配和释放操作。当后续为对象分配内存时,内存过大会存不进去一些碎片内存。
- 停顿时间:标记清除算法在执行垃圾回收时,需要停止应用程序的执行,进行标记和清除操 作。这会导致一段时间的停顿,可能会对应用程序的响应性产生影响。
2 标记整理算法
与标记清除算法相比,标记整理算法多了一个整理阶段与对象引用的更新。
2.1 整理阶段(Compact Phase)
在整理阶段,所有存活的对象都会被移动到一起,形成连续的内存块。这个过程中,同时进行了内存的压缩和重排,以消除内存碎片。
移动对象的步骤如下:
- 从堆的起始位置开始,遍历所有对象。
- 对于标记为存活的对象,将其移动到堆的前部,并更新对象引用的地址。
- 移动完所有存活对象后,将堆指针指向堆的末尾,此时堆的前部就是连续的空闲内存区域。
2.2 更新引用
在整理阶段完成后,需要更新所有指向移动对象的引用,使其指向对象的新地址。
2.3 优缺点
优点
解决内存碎片问题:标记-整理算法通过整理阶段将存活对象移动到一起,消除了标记清除算法中产生的内存碎片。这样可以提高后续的内存分配效率。
缺点
停顿时间:标记-整理算法在整理阶段需要移动对象,并更新对象引用的地址。这个过程需要消耗较多的时间,并且需要停止应用程序的执行。
3 复制算法
1.内存分为两个相等大小的半区:复制算法将内存分为两个相等大小的半区(From区和To区)。在垃圾回收过程中,只使用其中一个半区来分配对象,而另一个半区则保持空闲。并进行标记。
2.存活对象复制:复制算法通过从根对象开始,遍历对象图并标记所有可达的对象。然后,将所有存活的对象从From区复制到To区,按照顺序紧凑排列。
3.内存交换:一旦复制完成,From区和To区的角色互换,即To区变为新的From区,而原来的From区成为新的To区。这样,下一次垃圾回收时,将使用新的To区来进行对象的复制。
3.1 优缺点
优点
-
消除内存碎片:由于复制算法将存活对象紧凑排列,不会产生内存碎片。
-
简单高效:复制算法的实现相对简单,只需要将存活对象复制到另一个半区,并交换两个半区的角色。
-
低停顿时间:由于复制算法只需要复制存活对象,并且不需要进行标记和清除操作,所以它的停顿时间通常较短。
缺点
内存开销较大:复制算法需要两倍于实际存活对象大小的内存空间,因为每次垃圾回收时,需要将存活对象复制到另一个半区。这可能导致内存的浪费,尤其是在存活对象较多的情况下。
4 分代回收
实际的JVM垃圾回收中,并不会只使用一种算法,而是将多种算法结合起来使用。
4.1 MinorGC
Minor GC(新生代垃圾回收)是指对新生代进行的垃圾回收过程。在新生代中,通常使用复制算法来进行垃圾回收。
- 默认对象分配至伊甸园区,当伊甸园区内存不足时,系统会执行一次可达性分析算法,将存活对象通过复制算法复制到幸存区To中,并将伊甸园区的死亡对象回收。
- 然后对此时在幸存区中的对象进行可达性分析,将死亡的对象进行回收,同时为存活的对象年龄加,然后将幸存区From和幸存区To的地址进行交换并且对此时幸存区的对象的年龄加一。
4.2 对象的晋升
- 幸存区中的对象在经过多次Minor GC后,其年龄会递增。当对象的年龄达到一定的阈值(通常是15),对象会被晋升到老年代。
- 如果幸存区无法容纳幸存的对象,即幸存区的空间不足以容纳对象的存活集合,那么这些对象会被晋升到老年代。
4.3 FullGC
当新生代与老年代的内存都不足时,会触发FullGC,FullGC会从新生代到老年代全局进行一次垃圾回收,若回收后内存仍然不足会出现OOM。
4.4 STW
Stop the World是指在进行垃圾回收时,应用程序的执行被暂停的现象。这种停顿是因为在垃圾回收过程中,发生了内存地址的交换与变更,所以需要暂停其他用户的线程,先进行垃圾回收线程,垃圾回收线程结束才会继续进行其他线程。MinorGC与FullGC都会发生STW,MinorGC发生的STW较短。
4.5 分带回收相关参数
可以通过在Java代码中使用System.setProperty()
方法来动态设置这些参数。
System.setProperty("Xmn", "256m");
System.setProperty("Xmx", "1g");
System.setProperty("XX:MaxTenuringThreshold", "15");
垃圾回收器
1 串行垃圾回收器
串行垃圾回收器是最简单的垃圾回收器类型。它使用单线程执行垃圾回收操作,即在进行垃圾回收时,应用程序的执行会被暂停。串行回收器适用于简单的单线程应用程序或小型设备,因为它的停顿时间较长,吞吐量较低,但对系统资源的消耗较少。
1.1 回收算法
采用复制算法进行垃圾回收。
1.2 回收器种类
Serial对应新生代,SerialOld对应老年代。
1.3 开启指令
XX:+UseSerialGC = Serial + Serialold //开启串行垃圾回收
1.4 安全点(Safe Point)
指程序执行过程中的一个稳定状态,其中所有线程都处于安全位置,即线程执行时不会被垃圾回收器中断。在安全点上,垃圾回收器可以安全地开始垃圾回收操作。在串行垃圾回收结束后,所有被暂停的线程会从安全点继续执行。垃圾回收线程在串行垃圾回收完成后会进入等待状态。
2 吞吐量优先垃圾回收器 -- 并行
吞吐量优先垃圾回收器旨在最大化应用程序的吞吐量,即单位时间内完成的工作量。它通过并行执行垃圾回收操作来实现高吞吐量。Parallel Scavenge和Parallel Old是典型的吞吐量优先垃圾回收器组合。吞吐量优先回收器适用于注重整体性能而对停顿时间要求相对较低的应用程序。
2.1 运行示意![](https://i-blog.csdnimg.cn/blog_migrate/1fb257828734cdb8c35e1ce88767f928.png)
2.2 回收器种类以及对应回收算法
ParalleGC、ParalleOldGC :复制算法
3 响应时间优先垃圾回收器 - 并发
响应时间优先垃圾回收器旨在最大限度地减少应用程序的停顿时间,以提供更好的响应性能。这些回收器会在应用程序执行的同时进行垃圾回收操作,以减少停顿时间。其中最知名的是G1垃圾回收器(Garbage-First)。响应时间优先回收器适用于对低停顿时间要求较高的应用程序,如交互式应用程序或需要快速响应用户操作的应用程序。
3.1 运行示意
- 初始标记(Initial Mark):在此阶段,CMS回收器会暂停应用程序的执行,标记出所有的根对象和直接可达的对象。这个阶段是并发的,但只标记了一部分对象,并没有标记整个对象图。
- 并发标记(Concurrent Mark):在初始标记阶段之后,CMS回收器会与应用程序并发执行,进行对象图的遍历和标记。它会通过Tracing算法遍历所有可达对象,并在标记位图中记录这些对象的状态。
- 重新标记(Remark):在并发标记阶段完成后,CMS回收器会再次暂停应用程序的执行,进行最终的标记。它会处理在并发标记期间发生变化的对象,并确保所有的存活对象都被准确标记。
- 并发清除(Concurrent Sweep):在重新标记阶段之后,CMS回收器会与应用程序并发执行,清理并回收标记为垃圾的对象。这个阶段不会对对象进行移动或整理,而是简单地将垃圾对象的内存空间标记为可重用。
3.2 回收器种类以及对应的算法
CMS:标记清除算法 --- JDK8之前默认垃圾回收算法
G1:标记整理算法 --- JDK6体验 JDK7支持 JDK9默认垃圾回收器
4 G1垃圾回收器详解
G1(Garbage-First)垃圾回收器是一种现代化的垃圾回收器,引入了一种基于区域(Region)的内存布局和收集策略。下面是对G1垃圾回收算法的详细解释:
- 区域划分:G1将Java堆划分为多个大小相等的区域,每个区域的大小通常为1MB到32MB。这些区域可以是年轻代(Young Generation)或老年代(Old Generation)。
- 并发标记:G1使用并发标记算法,在应用程序运行的同时进行垃圾标记。并发标记从一组被选定的根对象开始,通过遍历对象图并标记可达对象。这个过程是并发的,与应用程序的执行并行进行。
- 混合收集:G1采用了混合收集的策略。它将垃圾回收过程分为多个阶段,其中包括并发标记、初始标记、最终标记和整理。在初始标记和最终标记阶段,应用程序会短暂停止,以标记直接可达的对象和处理并发标记阶段期间发生的变化。
- 垃圾优先:G1的名字"Garbage-First"表明其垃圾回收的优先目标。它优先回收包含最多垃圾的区域,以最大程度上提高回收效率。这有助于减少回收器引起的停顿时间。
- 区域优先回收:G1以区域为单位进行回收,而不是整个堆。它会选择包含最多垃圾的区域进行回收,称为"Garbage-First"区域。这样可以更快地回收大部分垃圾,并避免全堆扫描的开销。
- 空闲区域回收:G1通过空闲区域回收来释放不再使用的内存。它会将存活对象移动到连续的区域中,并清理未使用的区域。这种整理过程有助于确保内存的连续性,减少内存碎片化的影响。
- 停顿时间可控:G1的设计目标之一是可控制的停顿时间。通过将垃圾回收过程划分为多个独立的阶段,并根据用户指定的最大停顿时间进行计划,G1尽力在满足停顿时间目标的前提下完成垃圾回收。