第四节 JVM垃圾回收算法

亮子 2021-10-07 20:56:32 17363 0 0 0

1、什么是垃圾回收

程序的运行必然需要申请内存资源,无效的对象资源如果不及时处理就会一直占有内存资源,最终将导致内存溢出,所以对内存资源的管理是非常重要了。

2、什么样的对象需要回收

1)、引用计数法

  • 引用计数是历史最悠久的一种算法,最早George E. Collins在1960的时候首次提出,50年后的今天,该算法依然被很多编程语言使用。

  • 原理
    假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失效时,对象A的引用计数器
    就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。

  • 优缺点

  • 优点:
    实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。
    在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember 错误。
    区域性,更新对象的计数器时,只是影响到该对象,不会扫描全部对象。
  • 缺点:
    每次对象被引用时,都需要去更新计数器,有一点时间开销。
    浪费CPU资源,即使内存够用,仍然在运行时进行计数器的统计。
    无法解决循环引用问题。(最大的缺点)
class TestA{
 public TestB b;
}

class TestB{
 public TestA a;
}

//虽然a和b都为null,但是由于a和b存在循环引用,这样a和b永远都不会被回收。
public class Main{
   public static void main(String[] args){
       A a = new A();
       B b = new B();
       a.b=b;
       b.a=a;
       a = null; //释放资源
       b = null; //释放资源
   }
}

2)、可达性分析算法

通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,就说明从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的,就是可以回收的对象。

可达性分析算法

在JVM虚拟机中,可作为GC Roots的对象包括以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象,譬如字符串常量池(String T able)里的引用。
  • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象。反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

3)、对象的引用

在java中,对象的引用分为:强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种。

  • 强引用
    在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。
    无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  • 软引用
    用来描述一些还有用,但非必须的对象。
    只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 弱引用
    用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能存活到下一次垃圾收集发生为止
    当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用
    最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。该对象是会被垃圾回收器回收
    为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

3、如何回收垃圾

自动化的管理内存资源,垃圾回收机制必须要有一套算法来进行计算,哪些是有效的对象,哪些是无效的对象,对于无效的对象就要进行回收处理。

常见的垃圾回收算法有:标记清除法、标记压缩法、复制算法、分代算法等。

1)、标记清除算法

标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。
标记:从根节点开始标记引用的对象。
清除:未被标记引用的对象就是垃圾对象,可以被清理。

标记清除法可以说是最基础的收集算法,因为后续的收集算法大多都是以标记-清除算法为基础,对其缺点进行改进而得到的。

标记前

标记前

标记后

标记后

回收后

回收后

优缺点:

可以看到,标记清除算法解决了引用计数算法中的循环引用的问题,没有从root节点引用的对象都会被回收。

同样,标记清除算法也是有缺点的:
效率较低,标记和清除两个动作都需要遍历所有的对象,并且在GC时,需要停止应用程序,对于交互性要求比较高的应用而言这个体验是非常差的。
通过标记清除算法清理出来的内存,碎片化较为严重,因为被回收的对象可能存在于内存的各个角落,所以清理出来的内存是不连贯的。

2)、标记压缩算法

标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。

标记压缩算法

优缺点:

优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,同时,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有有一定的影响。

3)、复制算法

复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制
到另一个内存空间中,然后将该内存空间清空,交换两个内存的角色,完成垃圾的回收。
如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方式并且效率比较高,反之,则不适合。

复制算法

优缺点

  • 优点:
    在垃圾对象多的情况下,效率较高
    清理后,内存无碎片

  • 缺点:
    在垃圾对象少的情况下,不适用,如:老年代内存
    分配的2块内存空间,在同一个时刻,只能使用一半,内存使用率较低

4、垃圾回收算法的应用

目前使用的是分代算法

在堆内存中,有些对象短暂存活有些则是长久存活,所以需要将堆内存进行分代,将短暂存活的对象放到一起,进行高频率的回收,长久存活的对象集中放到一起,进行低频率的回收,这样才能够更加合理的利系统资源。

分代算法其实就是这样的,根据回收对象的特点进行选择,在jvm中,年轻代适合使用复制算法,老年代适合使用标记清除或标记压缩算法。

垃圾回收的相关概念:

  • 部分收集(Partial GC)
  • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
  • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。
  • 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。
  • 整堆收集(Full GC)

参考文档