JVM垃圾回收详解

通常,我们在写java程序的时候,似乎很少关注内存分配和垃圾回收的问题。因为,这部分工作,JVM已经帮我们自动实现了。

这样看起来,好像很美好,但是任何事情都有两面性。虽然JVM会自动的进行垃圾回收,但是,如果遇到有些问题,JVM自己也处理不了呢?

因此,我们需要了解一下JVM垃圾回收是怎样运作的,这样才能在遇到问题的时候,有的放矢。所以,今天就来聊一聊JVM的垃圾回收吧。

首先,思考一下,为什么需要进行垃圾回收?

我们知道,在创建对象的时候,Java会把对象的内容放到堆中。随着时间的推移,堆中的对象肯定会越来越多,但是,堆的大小是有限制的。如果,我们不进行垃圾回收,也就是把无用的对象进行清除和回收,那么JVM将不堪重负,最终导致内存泄漏。

既然我们需要进行垃圾回收,那么,首先得知道什么是垃圾。

在垃圾收集器对堆内存进行回收前,会先判断哪些对象还在“存活”,哪些对象已经“死去”(即不可能再被任何途径使用的对象),这些“死去”的对象,就是我们需要进行回收的垃圾。

那么,通过什么方式去判定是否为垃圾呢?(即判定对象是否存活)

引用计数算法(已淘汰)

引用计数算法,是指给对象中添加一个引用计数器,每当有一个地方引用它时,计数器的值就加1,当引用失效时,计数器的值就减1。当计数器值为0时,该对象就会被回收。

可以说,引用计数算法的实现非常简单,判定效率也很高。但是,我们忽略了一个问题,在Java中,对象之间是可以互相循环引用的。如果,两个对象之间互相循环引用,那么就会导致,它们之间的引用计数都不为0(都在等待对方释放资源),因此,就无法通知垃圾收集器回收它们。

可达性分析算法

这个算法的思想就是,通过一系列被称为“GC Roots”的对象作为起点,然后向下搜索,所走过的路径被称为引用链。当一个对象到 GC Roots之间没有任何引用链时(即从GC Roots到该对象不可达),则证明该对象是不可用的。

JVM垃圾回收详解

这个算法解决了循环引用的问题,只要对象无法与GC Root之间建立直接或间接的连接,就会判定为可回收对象。

那么,什么对象可以作为GC Root呢?一般分为以下四种:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中引用的对象。

既然已经确定了哪些垃圾可以被回收,那么就需要垃圾收集器进行垃圾回收了,我们来了解一下几种比较常见的的垃圾收集算法。

标记清除算法

file

是最基础的一种收集算法,分为标记和清除两个阶段。首先,把需要回收的对象标记出来,然后再把他们清除掉。如上图所示,所有可回收的对象会变成未使用的一片区域。

标记清除算法逻辑清晰,易于操作。但是,我们可以看到,未使用的内存区块都不是连续的,因此,此算法会产生很多的内存碎片。这样,当一些较大的对象需要分配空间的时候,就找不到足够的连续内存来存储,因此会提前触发GC,同时也浪费了很多的内存空间(内存空间太小,导致不可用)。

复制算法

file

复制算法,是指把内存区域划分为大小相等的两块区域。每次只使用其中的一块,当这一块内存用完了,就把所有存活的对象复制到另一块上面,最后再把已使用过的内存空间一次清理掉。

这样,就可以保证内存区域的连续性,不会产生内存碎片,实现简单,运行高效。但是,这样的话只有使用原来一半的内存,代价也太高了。

标记整理算法

file

标记整理算法,标记过程和标记清除算法一样,但是后续不是进行清除,而是先整理,让所有存活的对象都向一端移动,然后再清除另一端的内存区域。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfpgg.html