第四章 实战Java虚拟机——垃圾回收概念与算法

本文深入解析垃圾回收机制,包括垃圾的定义、识别方法如引用计数法与可达性分析,以及各种垃圾回收算法如标记清除、复制算法、标记压缩、分代算法和分区算法。同时,探讨了对象的可触及性及其强度,如强引用、软引用、弱引用和虚引用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、垃圾回收

2、什么垃圾

垃圾回收,特指存在于内存中,不会再被使用的对象。

3、识别垃圾

           ①引用计数法:引用计数法实现很简单,只需要为每个对象配备一个整型的计数器即可。对于对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能在被使用。但是,引用计数器存在两个严重的问题:

  • 无法处理循环引用的问题。因此,在java的垃圾回收器中,没有使用这种算法。

  • 引用计数器要求在每次因引用产生和消除的时候,需要伴随着一个加法操作和减法操作,对系统性能会有一定的影响。

由于单纯的引用计数算法隐含着循环引用和性能问题,Java虚拟机并未选择此算法作为垃圾回收算法。

②对象是否可达

可达对象:指通过根对象进行引用搜索,最终可以达到的对象。

不可达对象:通过跟对象进行引用搜索,最终没有被引用到的对象。

4、回收垃圾

4.1、标记清除

标记清除算法是现代垃圾回收算法的思想基础。标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。标记清除算法可能产生的最大的问题是空间碎片。

4.2、复制算法

复制算法的核心思想是:将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾的回收。如果系统中的垃圾对象比较多,复制算法需要复制的存活的对象数量就会相对较少。因此,在真正需要垃圾回收的时刻,复制算法的效率是很高的。又由于对象是在垃圾回收过程中,统一被复制到新的内存空间中的,因此,可确保回收后的内存空间是没有碎片的。但是,复制算法的代价却是将系统内存折半,因此,单纯的复制算法也难以让人接受。

在Java中新生代串行垃圾回收中,使用了复制算法的思想。将内存区域分为新生代、老年代。新生代分为Eden区、from区、to区3部分。其中from区和to区视为可复制的两块大小相同、地位相等、且可进行角色互换的空间块。from区和to区也称为survivor区。即幸存空间,用于存放未被回收的对象。

在垃圾回收时,Eden区中的存活的对象会被复制到未使用的survivor区中(假设to区),正在使用的survivor区中(假设from区)的年轻对象被复制到to区(大对象,或者老年对象会直接进入老年代,如果to区已满,则对象也会直接进入老年代)。此时,Eden区和from区中的剩余对象就是垃圾对象,可以直接清空,to区则存放此次回收后的存活的对象。这种改进的复制算法,既保证了空间的连续性,有避免了大量内存空间浪费。复制算法比较适合新生代,因为在新生代,垃圾对象通过多于存活对象,复制算法的效果比较好。

4.3、标记压缩

标记压缩算法是一种老年代的回收算法。它在标记算法的基础上做了一些优化。和标记清除算法一样,标记压缩算法也首先需要从根节点开始,对所有可达的对象做一次标记。但之后,它并不是简单地清理未标记的对象,而是将所有存活的对象压缩到内存的一端。之后,清理边界外所有的空间。这种算法既避免了碎片的产生,又不需要两块相同的内存空间,因此,性价比比较高。

4.4、分代算法

分代算法,将内存区间根据对象的特点分成几块,根据每块内存空间的特点,使用不同的回收算法,以提高垃圾回收的效率。

4.5、分区算法

分代算法将按照对象的生命周期长短划分成两部分,分区算法将整个堆空间划分成连续的不同小区间。没法小区间都独立使用,独立回收。这种算法的好处是可以控制一次回收多少个小区间。一般来说,在相同条件下,堆空间越大,一次GC时所需要的时间就越长,从而产生的停顿也越长。为了更好控制GC产生的停顿时间,将一块大的内存区域分割成多个小块,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次GC所产生的停顿。

5、判断可触及性

垃圾回收的基本思想是考察每个对象的可触及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说,此对象需要被回收。但事实上,一个无法触及的对象有可能在某一条件下“复活”自己,如果是这样,那么对他的回收就是不合理的,为此,需要给出一个对象可触及性状态的定义,并规定在什么状态下,才可以安全地回收对象。

可触及的:从根节点开始,可以到达的对象。

可复活的:对象的所有引用都被释放,但是对象有可能在finalize()函数中复活。

不可触及的:对象的fianlize()函数被调用,并且没有复活,那么就会进入不可触及状态,不可触及的对象不可能被复活,因为finalize()函数只会别调用一次。

5.1可触及性强度

强引用:强引用就是程序中一般使用的引用类型,强引用的对象是可触及的,不会被回收。相对的,软引用、弱引用和虚引用的对象是软可触及、弱可触及、虚可触及的,在一定条件下,都是可以被回收的。

软引用:软引用是比强引用弱一点的引用类型。一个对象只有持有了软引用,那么当堆空间不足时,就会被回收。所以,软引用对象不会引起内存溢出。每个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时,软引用的对象就会进入引用队列。通过引用队列,可以跟踪对象的回收情况。

弱引用:发现即回收。弱引用是一种比软引用较弱的引用类型。在系统GC时,只要发现弱引用,不管系统堆空间使用清空如何,都会讲对象进行回收。但是,由于垃圾回收器的线程通常优先级很低,因此,并不一定能够很快地发现持有弱引用的对象。在这种情况下,弱引用对象可以存在较长的时间。一旦一个弱引用对象被垃圾收集器回收,便会加入到一个注册的引用队列中。弱引用和软应用一样,在构造弱引用是,也可以指定一个引用队列,当弱引用对象被回收时,就会被加入指定的引用队列,通过队列可以跟踪对象的回收情况。软引用和弱引用都非常适合来保存有些可有可无的缓存数据。如果这么做,当系统内存不足时,这些缓存数据被回收,不会导致内存溢出。而当内存资源充足时,这些缓存数据又可以存在相当长的时间,从而起到加速系统的作用。

虚引用:虚引用是所有引用类型最弱的一个。一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。但试图通过虚引用的get()方法取得强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程。当垃圾回收器准备回收一个对象时,若发现它还有虚引用,就会在回收对象后,将这个虚引用加入引用队列,以通知引用程序对象的回收情况。由于虚引用可以跟踪对象的回收时间,因此,也可以将一些资源的释放操作放置在虚引用中执行和记录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值