图解JVM垃圾内存回收算法

  

<强>
  

  

首先,我们要讲的是JVM的垃圾回收机制,我默认准备阅读本篇的人都知道以下两点:

  
      <李> JVM是做什么的李   <李> Java堆是什么李   
  

因为我们即将要讲的就是发生在JVM的Java堆上的垃圾回收,为了突出核心,其他的一些与本篇不太相关的东西我就一笔略过了
  

  

众所周知,Java堆上保存着对象的实例,而Java堆的大小是有限的,所以我们只能把一些已经用完的,无法再使用的垃圾对象从内存中释放掉,就像JVM帮助我们手动在代码中添加一条类似于c++的免费语句的行为
  

  

然而这些垃圾对象是怎么回收的,现在不知道没关系,我们马上就会讲到
  

  


  

  

在了解具体的GC(垃圾回收)算法之前,我们先来了解一下JVM是怎么判断一个对象是垃圾对象的
  顾名思义,垃圾对象,就是没有价值的对象,用更严谨的语句来说,就是没有被访问的对象,也就是说没有被其他对象引用,这就牵引出我们的第一个判断方案:引用计数法
  

  

<强>引用计数法
  

  

这种算法的原理是,每有一个其他对象产生对一个对象的引用,则一个对象的引用计数值就+ 1,反之,每有一个对象对一个对象的引用失效的时候,一个对象的引用计数值就1,当一个对象的引用计数值为0的时候,其就被标明为垃圾对象
  

  

这种算法看起来很美,好了c++的解应该知道,c++的智能指针也有类似的引用计数,但是在这种看起来“简单”的方法,并不能用来判断一个对象为垃圾对象,我们来看以下场景:

  

图解JVM垃圾内存回收算法

  

在这个场景中,一个对象有B对象的引用,B对象也有一个对象的引用,所以这两个对象的引用计数值均不为0,但是,A, B两个对象明明就没有任何外部的对象引用,就像大海上两个紧挨着的孤岛,即使他们彼此依靠着,但仍然是孤岛,其他人过不去,而且由于引用计数不为0,也无法判断为垃圾对象,如果JVM中存在着大量的这样的垃圾对象,最终就会频繁抛出伯父异常,导致系统频繁崩溃
  

  

总而言之,如果有人问你为什么JVM不采用引用计数法来判断垃圾对象,只需要记住这一句话:引用计数法无法解决对象循环依赖的问题
  

  

<>强可达性分析法
  

  

引用计数法已经很接近结果了,但是其问题是,为什么每有一个对象来引用就要给引用计数值+ 1,就好像有人来敲门就开一样,我们应该只给那些我们认识的,重要的人开的门,也就是说,只有重要的对象来引用时,才给引用计数值+ 1
  

  

但是这样还不行,因为重要的对象来引用只要有一个就够了,并不需要每有一个引用就+ 1,所以我们可以将引用计数法优化为以下形式:

  
  

给对象设置一个标记,每有一个“重要的对象”来引用时,就将这个标记设为真,当没有任何“重要的对象“引用时,就将标记设为false,标记为假的对象为垃圾对象

     

这就是可达性分析法的雏形,我们可以继续进行修正,我们并不需要主动标记对象,而只需要等待垃圾回收时找到这些“重要的对象”,然后从它们出发,把我们能找到的对象都标记为非垃圾对象,其余的自然就是垃圾对象
  

  

我们将上文提到的“重要的对象“命名为GC根,这样就得到了最终的可达性分析算法的概念:

  
  

创建垃圾回收时的根节点,称为GC根,从GC根出发,不能到达的对象就被标记为垃圾对象

     

其中,可以作为GC根的区域有:

  
      <李>虚拟机栈的栈帧中的局部变量表   <李>方法区的类属性和常量所引用的对象李   <李>本地方法栈中引用的对象李   
  

换句话说,GC根就是方法中的局部变量,类属性,以及常量
  

  


  

  

终于到本文的重点了,我们刚刚分析了如何判断一个对象属于垃圾对象,接下来我们就要重点分析如何将这些垃圾对象回收掉
  

  

<强>标记——清除算法
  

  

标记——清除很容易理解,该算法有两个过程,标记过程和清除过程,标记过程中通过上文提到的可达性分析法来标记出所有的非垃圾对象,然后再通过清除过程进行清理

图解JVM垃圾内存回收算法