【面试】Java垃圾回收算法解析:优化内存管理的关键步骤

547 阅读5分钟

一、垃圾回收机制概念

垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间 的一种机制。

二、什么是垃圾?

无任何对象引用的对象,就是垃圾。

三、如何定位垃圾?

3.1 引用计数算法(Reference Counting Collector)

image.png

堆中每个对象(不是引用)都有一个引用计数器。当一个对象被创建并初始化赋值后,该变量计数设置为1。每当有一个地方引用它时,计数器值就加1(a = b, b被引用,则b引用的对象计数+1)。当引用失效时(一个对象的某个引用超过了生命周期(出作用域后)或者被设置为一个新值时),计数器值就减1。任何引用计数为0的对象可以被当作垃圾收集。当一个对象被垃圾收集时,它引用的任何对象计数减1。

优点: 引用计数收集器执行简单,判定效率高,交织在程序运行中。对程序不被长时间打断的实时环境比较有利(OC的内存管理使用该算法)。

缺点: 难以检测出对象之间的循环引用。同时,引用计数器增加了程序执行的开销。所以Java语言并没有选择这种算法进行垃圾回收。

3.2 根搜索算法(Tracing Collector)

image.png

1》通过名为“GC Roots”的对象作为起始点,寻找对应的引用节点。

2》找到这些引用节点后,从这些节点开始向下继续寻找它们的引用节点。

3》搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。

Java和C#中都是采用根搜索算法来判定对象是否存活的。

四、回收什么?

回收的是无任何引用的对象占据的内存空间而不是对象本身。

五、回收垃圾算法

回收垃圾对象内存的算法主有三种。

5.1 标记—清除算法

标记-清除算法是最基础的算法,它分为两部分:标记和清除。

image.png

标记: 首先标记出需要回收的对象。

清除: 在标记完成后统一回收掉所有被标记的对象,清除掉的垃圾就会变成未使用的内存区域,等待再次被使用。

优点: 不需要进行对象的移动,并且仅对不存活的对象进行处理,在存活对象比较多的情况下极为高效。

缺点:

(1)标记和清除过程的效率都不高 (这种方法需要使用一个空闲列表来记录所有的空闲区域以及大小。对空闲列表的管理会增加分配对象时的工作量)。

(2)标记清除后会产生大量不连续的内存碎片

我们知道,开辟内存空间,需要的是连续的内存区域,假设上图中2M的内存空间是清理过后待使用的内存空间,而此时我们需要开辟的是4M的内存空间,但是不满足这次的分配所需的大小,这样2M这个内存空间就没办法使用了。

注:虽然空闲区域的大小是足够的,但却可能没有一个单一区域能够满足这次分配所需的大小,因此本次分配还是会失败(在Java中就是一次OutOfMemoryError)不得不触发另一次垃圾收集动作。

5.2 复制算法

image.png

可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

优点: 保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。

缺点: 需要一块能容纳下所有存活对象的额外的内存空间。因此,可一次性分配的最大内存缩小了一半。

5.3 标记—整理算法

image.png

标记: 首先标记出需要回收的对象。

整理: 让所有存活的对象都向一端移动。

清除: 再清理掉端边界以外标记可回收的的内存区域。

优点: 解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。

缺点: 它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。

总结

为什么要引入垃圾回收机制呢?不进行垃圾回收,内存迟早要被消耗完,随着应用程序所应付的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序的正常进行。

注意: 由于大多商业JVM的垃圾收集器都遵循了分代收集的理论进行设计,所以目前虚拟机使用的回收算法是 【分代收集算法】,在不同年代使用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行分配担保,所以只能使用标记清除或者标记整理算法。