JVM垃圾回收算法

一、JVM垃圾回收算法概述

Java虚拟机在回收垃圾过程中主要有垃圾标记阶段和垃圾清除阶段。垃圾标记阶段任务是在JVM进行垃圾回收前判断哪些是存活对象,哪些是死亡对象,只有被标记为死亡的对象垃圾回收器才会回收,释放其占用的内存空间。判断垃圾存活一般有两种方式分别是引用计数算法和可达性分析算法;垃圾清除阶段任务是当垃圾成功区分死亡对象和存活对象后,垃圾回收器接来的任务是清除哪些已死亡的对象,释放占用的内存空间,以便有足够内存空间来分配新的对象,在JVM中常见的垃圾回收算法有三种分别是标记-清除算法(Mark-Sweep),复制算法(Copying)、标记整理算法(Mark-Compact)。

二、判断对象存活

引用计数算法

引用计数算法(Reference Counting)就是为每一个对象保存一个整型的引用计数属性,用来记录该对象被引用的情况。比如一个对象A,如果任何对象引用了对象A,则A的引用计数器就会加1,当引用失效时引用计数器就会减一,只要引用计数器为0就表示此时A对象没有引用,那么回收器可以对其进行回收。
引用计数算法有如下优点:

  • 实现简单,垃圾对象容易识别,判断效率高,回收没有延迟。

引用计数算法有如下缺点:

  • 由于需要单独字段存储计数器,这样会增加了内存空间开销,同时由于每次赋值对象都要维护引用计数属性(对引用计数属增减操作)这样也会增加了时间开销。
  • 最致命的一个缺陷是由于引用计数算法无法解决循环引用问题,有可能会导致内存泄露,因此Java并没有使用该算法判断对象存活。

可达性分析算法

可达性分析算法解决引用计数算法的循环引用问题,具体实现思路是以根对象集合GC Root(所谓GC Roots就是一组活跃的引用)为启始点从上往下搜索,如果一个对象没有被GC Root任何引用链相连则该对象就会被标记为垃圾,等待被回收。
在JVM中GC Roots的对象范围有以下几种:

  • 虚拟机栈中的局部变量表引用的对象
  • 本地方法栈中 JNI (Native方法)引用的对象
  • 方法区中类的静态属性,常量引用的对象
  • Java虚拟机内部的引用
  • 字符串常量池String Table里的引用。

可达性分析算法具备实现简单和判断高效的特点,但是如果要使用可达性分析算法来判断对象是否可以回收,那么必须要在分析工作时进行stop the world,否则无法保证分析结果的准确性。

三、垃圾回收算法

标记-清除算法

标记清除算法(Mark-Sweep)是一种非常基础和常见的垃圾回收算法,该算法被J.McCarthy等人在1960年提出并应用于List语言。具体实现思路如下图:
JVM垃圾回收算法_第1张图片
首先从GC Roots对象为起始点进行遍历,标记所有可达的对象,一般在对象头记录可达对象,标记完成后然后在推内存中从头到尾进行线性遍历,如果发现某个对象的对象头没有标记为可达对象就将其回收。
标记-清除算法的效率并不是很高,在进行垃圾回收时,需要停止整个应用程序,这样会导致用户体验差,同时这种方法清理出来的内存空间是不连续的,会生成内存碎片。

这里清除并不是指将释放的空间清空,而是将清除对象的内存地址保存在空闲地址列表,等有新的对象需要分配空间时,就从空闲地址列表获取,判断垃圾位置空间是否足够,如果够就存放。

复制算法

复制算法解决了标记清除算法在垃圾回收效率方面的缺陷,具体实现思路如下图:
JVM垃圾回收算法_第2张图片
复制算法将内存空间分为两块,每次只使用其中的一块,在垃圾回收时首先从GC Roots开始从上至下开始遍历,将可达的对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象。
复制算法在执行过程中没有标记,实现简单运行高效,复制过去以后保证了空间的连续性,不会出现碎片问题。缺点是需要两倍的内存空间,而且在复制过程中,由于对象的内存地址发生变化也需要修正指向对象数据的指针,这样不管是空间占用还是时间开销都是非常多的。

标记-整理算法

复制算法的高效性是建立在存活对象少,垃圾对象多得前提下的,这种情况在新生代经常发生,但是在老年代大部分对象都是存活对象,如果依然使用复制算法,由于存活对象较多,复制的成本也将很高,因此基于老年代垃圾回收特性需要使用其他的算法,所以标记-整理算法由此诞生。标记-整理算法具体实现思路如下图:
JVM垃圾回收算法_第3张图片
首先从GC Roots为起始点标记所有可达对象,完成后未被标记的对象将会被清理掉,然后将所有存活的对象压缩到内存的一端,按照内存地址来依次排列。标记-整理算法解决复制算法内存减半的高额代价问题,但从效率上来说,标记整理算法要低于复制算法。

小总结

三种算法对比如下:

标记-清除算法 复制算法 标记-整理算法
速度 中等 最快 最慢
空间开销 多(会产生碎片) 需要两倍的内存空间
移动对象

四、分代收集算法

目前几乎所有的垃圾回收器都是采用分代回收算法执行垃圾回收的,在hostSpot中基于分代的概念,垃圾回收器所使用的内存回收算法必须结合年轻代和老年代的所有特点。

  • 年轻代

年轻代特点内存区域相对老年代较小,对象生命周期短,存活率短,回收频繁,这种情况复杂算法回收整理速度是最快的,复杂算法的效率和当前存活对象大小有关。

  • 老年代

老年代区域较大,对象生命周期长,存活率高,回收不及年轻代频繁这种情况下存在大量存活对象,复杂算法明显变得不合适,一般使用标记-清除算法和标记-整理算法混合使用。

你可能感兴趣的:(Java,jvm)