Java垃圾收集机制

目录

前言

判断对象是否存活

引用计数算法

可达性分析算法

GC Root的产生

Java中的四种引用类型

1.强引用

强引用弱化方式

方式1:使对象指向null

方式2:使对象超出作用域范围

2.软引用

3.弱引用

4.虚引用

垃圾收集算法

分代收集理论

垃圾收集算法分类

1.标记-清除算法(Mark-Sweep)

标记-清除算法的特点或问题

2.标记-复制算法(Copying)

算法工作流程

标记-复制算法的特点

3.标记-整理算法(Mark-Compact)

算法工作流程

标记-整理算法的特点

总结

前言

Java中的垃圾收集器(Garbage Collector)负责管理堆内存中的对象,回收不再使用的对象以释放内存空间。

判断对象是否存活

垃圾收集器工作的前提是该对象确实为一个垃圾对象,因此在 回收前需要进行判断,这里有两种方式可以对对象进行判断,分别是引用计数算法和可达性分析算法

引用计数算法

引用计数算法是通过在对象中添加一个引用计数器,来记录该对象被其他对象引用的次数,当计数器为0的时候,表示该对象没有被其他对象引用,是一个可回收的垃圾对象。

当然,这种算法存在一个较明显的问题,就是无法应对对象的循环引用,例如a 对象引用了 b 对象,b 对象也引用了 a 对象,ab 对象却没有再被其他对象所引用了,其实正常来说这两个对象已经是垃圾了,因为没有其他对象在使用了,但是计数器内的数值却不是 0,所以引用计数算法就无法回收它们。

Java垃圾收集机制_第1张图片

可达性分析算法

可达性分析算法是通过引用链确认对象是否可用,也就是说在引用链上的对象即为可用对象,否则为垃圾对象。在该引用链上定义了“GC Root”起始节点集,引用链上的每个对象都可以通过GC Root找到。

例如下图中,Object 6Object 7Object 8彼此之前有引用关系,但是没有与"GC Roots"相连,那么就会被当做垃圾所回收。

Java垃圾收集机制_第2张图片

GC Root的产生

  1. 栈帧中的本地变量和参数:正在执行的方法中的本地变量和参数可以作为GC Root,因为它们是当前线程活动的一部分。
  2. 静态变量:类的静态变量通常存储在方法区中,它们随着类的加载而被创建,并且在整个程序运行期间一直存在,因此它们被视为GC Root。
  3. 虚拟机内部的特殊引用:例如常量引用、系统类加载器等。
  4. JNI引用:在Java代码中使用了JNI(Java Native Interface)时,如果在本地代码中使用全局引用或弱全局引用,它们也会作为GC Root。

Java中的四种引用类型

垃圾回收器的工作时机除了与本身使用的算法相关,还与对象本身的引用类型相关。

1.强引用

强引用是最常使用的引用,通常是指向new 出来的对象的引用 例如 Object strongReference = new Object();,对于强引用,垃圾回收器是绝对不会进行回收操作的。就算是在内存空间不足的情况下,JVM也会通过抛出OutOfMemory来终止程序,而非回收强引用。

强引用弱化方式

强引用对象也存在不使用的情况,它的回收需要通过弱化,从而使垃圾回收器回收

方式1:使对象指向null

此时垃圾回收器会认为该对象不存在引用,然后根据GC算法对该对象进行回收。

例如:ArrayList集合的clear方法,就是通过遍历数组,将数组中的每个引用都指向null,使垃圾回收器能够对对象进行回收,达到清空的效果。

Java垃圾收集机制_第3张图片

方式2:使对象超出作用域范围

在Java中,对象的作用域通常由大括号({})来定义,包括类、方法和代码块。当对象的作用域结束时,即离开了其定义的代码块或方法,该对象就会超出其作用域范围。

简单来说就是当一个对象在其作用域内被创建时,在该作用域范围内,可以通过引用来访问和操作该对象。但是一旦离开了对象的作用域,该对象就不能再通过原有的引用来访问和操作了。比如说对象对象定义在方法内,当方法执行结束,JVM方法栈中的活动栈帧弹出时,该对象也就不在作用域范围内了。

2.软引用

软引用也是一种较强的引用类型,与强引用的不回收不同,软引用可以在内存空间补不足时被垃圾回收器回收。通常用于内存敏感内容的高速缓存。

举个例子,在下图中,模拟了一个软引用,分别测试内存溢出与内存不溢出的情况下,断开引用后,软引用中的值

Java垃圾收集机制_第4张图片

内存充沛

内存不足

Java垃圾收集机制_第5张图片

在上面的例子上我们可以看到,当内存充足的情况下,即使软引用指向null,也不会被回收,一旦内存不足,软引用则被回收。

3.弱引用

弱引用是一种比软引用更弱的引用类型,与软引用不同,弱引用在下一次垃圾回收时一定会被垃圾回收器回收,无论内存是否充足。

Java垃圾收集机制_第6张图片

4.虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,完全不会对其生存时间构成影响,它就和没有任何引用一样,随时可能会被回收。主要用来跟踪对象被垃圾回收的活动,可以在垃圾收集时收到一个系统通知。

JDK1.2 之后,用 PhantomReference类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象。

Java垃圾收集机制_第7张图片

垃圾收集算法

说到垃圾收集算法就不得不说一个重要理论,即分代收集理论。下文详细介绍:

分代收集理论

分代收集理论基于一个观察:将对象的生命周期通常可以被划分为不同的阶段或代。根据对象的生命周期,垃圾回收器可以将内存划分为不同的代,然后针对不同代的对象应用不同的垃圾回收策略。目前主流JVM的垃圾收集器都遵循分代收集理论。

一般来说,分代收集理论将内存划分为至少两个代:年轻代、老年代

这是由于大部分对象在被创建后一段时间内会被频繁使用,但随着时间的推移,它们的生命周期会逐渐延长。因此,分代收集理论认为大部分对象是临时的,并且在它们的年轻阶段就会被回收,而只有部分对象会存活更久并进入老年代。

年轻代

被创建后一段时间内会被频繁使用,但随着时间的推移,它们的生命周期会逐渐延长。因此,分代收集理论认为大部分对象是临时的,并且在它们的年轻阶段就会被回收,而只有部分对象会存活更久并进入老年代。

老年代

在老年代中,垃圾回收器采用更长的回收周期和较少的垃圾回收操作。这是因为老年代中的对象通常具有较长的生命周期,并且相对稳定。典型的垃圾回收策略是标记-清除(Mark-Sweep)或标记-压缩(Mark-Compact)算法,其中垃圾回收器会标记并清除或压缩无效的对象,并回收相应的内存空间。

垃圾收集算法分类

1.标记-清除算法(Mark-Sweep)

标记-清除算法的整个工作过程可以分为两个阶段,分别是“标记”和“清除”。在标记阶段,首先垃圾收集器会标记出所有不需要回收的对象(这可以通过在对象的头部添加一个标记位来实现),然后进入清除阶段,将未标记的对象回收。

Java垃圾收集机制_第8张图片

标记-清除算法的特点或问题

A.效率问题:

如果执行垃圾收集的区域中,大部分对象是需要被回收的,则需要哦执行大量的标记和清除操作,导致效率变低。

B.内存空间问题:

标记清除后会产生大量不连续的碎片,空间碎片太多,会导致分配较大对象时,无法找到足够的连续空间,从而会触发新的垃圾收集动作。

C.暂停时间:

在标记和清除阶段中,垃圾回收器需要停止应用程序的执行,这可能导致较长的停顿时间,影响应用的响应性。

2.标记-复制算法(Copying)

标记-复制算法在内存上的维护上,将空间分成两个大小相同的区域,通常称为对象区域和空闲区域。

算法工作流程
  1. 初始化时,所有对象都在对象区域。
  2. 从根对象开始,标记所有可达对象,将其从对象区域复制到空闲区域,并在新位置上更新引用。
  3. 完成复制后,对象区域中所有未被复制的对象都被认为是垃圾。
  4. 清空对象区域,并将空闲区域与对象区域交换,使空闲区域变为下一次垃圾回收的对象区域。
  5. 重复上述过程,进行下一轮的垃圾回收。

Java垃圾收集机制_第9张图片

标记-复制算法的特点

A.高效的分配:

由于复制过程中对象是连续存放的,新对象的分配只需要简单的移动指针,效率较高。

B.暂停时间可控:

由于只需要复制存活对象,标记阶段的垃圾回收工作量相对较小,可以有效限制暂停时间。

C.内存利用率相对较低:

标记-复制算法会将堆内存的一半用于复制存活对象,因此相对于标记-清除算法会有一定的内存浪费。

D.效率降低

在对象存活率较高的情况下,需要进行较多内存间复制,导致效率降低

3.标记-整理算法(Mark-Compact)

标记-整理算法的主要思想是在标记阶段标记所有可达对象,并在标记完成后进行整理。整理阶段会将存活的对象向一端移动,然后清理掉边界之外的无效对象,以达到内存整理的目的。

算法工作流程
  1. 从根对象开始,标记所有可达对象。
  2. 在标记完成后,将存活对象向一端移动,并保持它们的相对顺序不变。这样,所有存活对象就在内存的一段连续区域中。
  3. 在移动过程中,将无效对象所占用的内存空间进行回收。可以通过向移动后存活对象的新位置复制数据来完成这一步骤。
  4. 清理掉边界之外的无效对象所占用的内存空间,使其变为可用于分配新对象的空闲内存。
  5. Java垃圾收集机制_第10张图片

标记-整理算法的特点

A.解决内存碎片化:

通过将存活对象移动到一端,并清理边界之外的无效对象,可以实现内存的整理,解决内存碎片问题。

B.高效的分配:

由于整理后存活对象在一段连续区域中,新对象的分配只需要简单的移动指针,效率较高。

C.暂停时间可控:

标记-整理算法的标记和整理阶段可以分别进行,可以有效限制暂停时间。

总结

标记-整理算法适用于老年代,而在年轻代通常采用其他垃圾回收算法,如标记-复制算法或其他更适合的算法。标记-整理算法通常与年轻代的标记-复制算法或其他算法配合使用,以实现整个堆的高效垃圾回收。

你可能感兴趣的:(jvm,java,开发语言)