目录
前言
判断对象是否存活
引用计数算法
可达性分析算法
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
对象,a
、b
对象却没有再被其他对象所引用了,其实正常来说这两个对象已经是垃圾了,因为没有其他对象在使用了,但是计数器内的数值却不是 0
,所以引用计数算法就无法回收它们。
可达性分析算法是通过引用链确认对象是否可用,也就是说在引用链上的对象即为可用对象,否则为垃圾对象。在该引用链上定义了“GC Root”起始节点集,引用链上的每个对象都可以通过GC Root找到。
例如下图中,Object 6
、Object 7
、Object 8
彼此之前有引用关系,但是没有与"GC Roots"相连,那么就会被当做垃圾所回收。
垃圾回收器的工作时机除了与本身使用的算法相关,还与对象本身的引用类型相关。
强引用是最常使用的引用,通常是指向new 出来的对象的引用 例如 Object strongReference = new Object();,对于强引用,垃圾回收器是绝对不会进行回收操作的。就算是在内存空间不足的情况下,JVM也会通过抛出OutOfMemory来终止程序,而非回收强引用。
强引用对象也存在不使用的情况,它的回收需要通过弱化,从而使垃圾回收器回收
此时垃圾回收器会认为该对象不存在引用,然后根据GC算法对该对象进行回收。
例如:ArrayList集合的clear方法,就是通过遍历数组,将数组中的每个引用都指向null,使垃圾回收器能够对对象进行回收,达到清空的效果。
在Java中,对象的作用域通常由大括号({})来定义,包括类、方法和代码块。当对象的作用域结束时,即离开了其定义的代码块或方法,该对象就会超出其作用域范围。
简单来说就是当一个对象在其作用域内被创建时,在该作用域范围内,可以通过引用来访问和操作该对象。但是一旦离开了对象的作用域,该对象就不能再通过原有的引用来访问和操作了。比如说对象对象定义在方法内,当方法执行结束,JVM方法栈中的活动栈帧弹出时,该对象也就不在作用域范围内了。
软引用也是一种较强的引用类型,与强引用的不回收不同,软引用可以在内存空间补不足时被垃圾回收器回收。通常用于内存敏感内容的高速缓存。
举个例子,在下图中,模拟了一个软引用,分别测试内存溢出与内存不溢出的情况下,断开引用后,软引用中的值
内存充沛
内存不足
在上面的例子上我们可以看到,当内存充足的情况下,即使软引用指向null,也不会被回收,一旦内存不足,软引用则被回收。
弱引用是一种比软引用更弱的引用类型,与软引用不同,弱引用在下一次垃圾回收时一定会被垃圾回收器回收,无论内存是否充足。
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,完全不会对其生存时间构成影响,它就和没有任何引用一样,随时可能会被回收。它主要用来跟踪对象被垃圾回收的活动,可以在垃圾收集时收到一个系统通知。
在 JDK1.2
之后,用 PhantomReference
类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get()
方法,而且它的 get()
方法仅仅是返回一个null
,也就是说将永远无法通过虚引用来获取对象。
说到垃圾收集算法就不得不说一个重要理论,即分代收集理论。下文详细介绍:
分代收集理论基于一个观察:将对象的生命周期通常可以被划分为不同的阶段或代。根据对象的生命周期,垃圾回收器可以将内存划分为不同的代,然后针对不同代的对象应用不同的垃圾回收策略。目前主流JVM的垃圾收集器都遵循分代收集理论。
一般来说,分代收集理论将内存划分为至少两个代:年轻代、老年代
这是由于大部分对象在被创建后一段时间内会被频繁使用,但随着时间的推移,它们的生命周期会逐渐延长。因此,分代收集理论认为大部分对象是临时的,并且在它们的年轻阶段就会被回收,而只有部分对象会存活更久并进入老年代。
年轻代
被创建后一段时间内会被频繁使用,但随着时间的推移,它们的生命周期会逐渐延长。因此,分代收集理论认为大部分对象是临时的,并且在它们的年轻阶段就会被回收,而只有部分对象会存活更久并进入老年代。
老年代
在老年代中,垃圾回收器采用更长的回收周期和较少的垃圾回收操作。这是因为老年代中的对象通常具有较长的生命周期,并且相对稳定。典型的垃圾回收策略是标记-清除(Mark-Sweep)或标记-压缩(Mark-Compact)算法,其中垃圾回收器会标记并清除或压缩无效的对象,并回收相应的内存空间。
标记-清除算法的整个工作过程可以分为两个阶段,分别是“标记”和“清除”。在标记阶段,首先垃圾收集器会标记出所有不需要回收的对象(这可以通过在对象的头部添加一个标记位来实现),然后进入清除阶段,将未标记的对象回收。
A.效率问题:
如果执行垃圾收集的区域中,大部分对象是需要被回收的,则需要哦执行大量的标记和清除操作,导致效率变低。
B.内存空间问题:
标记清除后会产生大量不连续的碎片,空间碎片太多,会导致分配较大对象时,无法找到足够的连续空间,从而会触发新的垃圾收集动作。
C.暂停时间:
在标记和清除阶段中,垃圾回收器需要停止应用程序的执行,这可能导致较长的停顿时间,影响应用的响应性。
标记-复制算法在内存上的维护上,将空间分成两个大小相同的区域,通常称为对象区域和空闲区域。
A.高效的分配:
由于复制过程中对象是连续存放的,新对象的分配只需要简单的移动指针,效率较高。
B.暂停时间可控:
由于只需要复制存活对象,标记阶段的垃圾回收工作量相对较小,可以有效限制暂停时间。
C.内存利用率相对较低:
标记-复制算法会将堆内存的一半用于复制存活对象,因此相对于标记-清除算法会有一定的内存浪费。
D.效率降低
在对象存活率较高的情况下,需要进行较多内存间复制,导致效率降低
标记-整理算法的主要思想是在标记阶段标记所有可达对象,并在标记完成后进行整理。整理阶段会将存活的对象向一端移动,然后清理掉边界之外的无效对象,以达到内存整理的目的。
A.解决内存碎片化:
通过将存活对象移动到一端,并清理边界之外的无效对象,可以实现内存的整理,解决内存碎片问题。
B.高效的分配:
由于整理后存活对象在一段连续区域中,新对象的分配只需要简单的移动指针,效率较高。
C.暂停时间可控:
标记-整理算法的标记和整理阶段可以分别进行,可以有效限制暂停时间。
标记-整理算法适用于老年代,而在年轻代通常采用其他垃圾回收算法,如标记-复制算法或其他更适合的算法。标记-整理算法通常与年轻代的标记-复制算法或其他算法配合使用,以实现整个堆的高效垃圾回收。