JVM中GC算法总结

Owen Jia 2021年03月04日 46次浏览

对于一个java工程师来说,平时可以说不太懂算法,也没有专门做过算法,这是可以说的。也许你正好从事了算法这个行业,那懂的就非常多了,但我们这里谈的“java工程师”不包括这类。

就是不是算法行业,对每个java工程师来说jvm涉及的算法都是要明白的,起码原理要能讲的明白。

定位垃圾

在jvm内存区用到了好几种算法,也是jdk发展历程中点点滴滴的经验积累。

多学几个算法总没错。

引用计数算法

这是一个非常基础的算法,目前的java堆不再使用这种,但是这个简单的算法思想却可以在日常开发中加以运用。

原理:
在对象中加入一个引用计数器,每当这个对象被引用,计数器就加一;当引用失效了,计数器就减一;当计数器为零时,就是不再被使用了。

可达性分析算法

jvm堆内存回收目前用的就是可达性分析,C#和Lisp也是用的这个算法。

原理:
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始跟进引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如何一个对象到GC Roots没有任何引用链相连,就说明该对象不会再被使用了。

GC Roots对象包括:

  • 虚拟机栈中引用的对象,如线程被调用方法的参数、局部变量、临时变量等。
  • 类静态属性引用的对象。
  • 常量引用的对象。
  • 本地方法栈中JNI引用的对象。
  • java虚拟机内部引用的对象。
  • 所有被同步锁持有的对象。
  • JMXBean、JVMTI中的回调、本地代码缓存等。

回收垃圾

JVM中基本采用了“分代收集”理论,jdk8一般我们可认为有“新生代”和“老年代”两种。

java对象两大特性:

  • 绝大多数对象都是朝生夕死的。
  • 熬过越多次垃圾收集的对象就越难以消亡。

标记-清除算法

这是最早的垃圾回收算法,1960第一次提出。

原理:
首先标记所有需要回收的对象,在标记完成后,统一回收掉被标记的对象,反之标记存活的对象,完成后把所有未标记的对象清楚。

该算法下的对象内存分三种:存活对象、可回收、未使用,在实际运用中标记-清除算法会导致内存碎片化严重(大对象内存需要无法获取),且清除效率不高。CMS垃圾收集器使用了标记-清除算法。

标记-复制算法

标记-复制算法又称“半区算法”,该算法下的内存分为两块相等的内存区域,并且每次只使用其中的一块。

原理:
将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这块内存使用完了,就将还存活的对象复制到另一块上,然后把已经使用过的内存清理掉。同样的步骤,两块内存重复进行下去。

该算法每次都闲置了一半的内存空间,同时当大多数对象存活情况下,复制到另一块内存的开销就会很多,效率就很低了。

标记-整理算法

这是在标记-复制和标记-清除算法基础上延伸出来的优化算法。

原理:
首先还是标记所有需要回收的对象,标记完成后不是清理,而是让所有存活的对象都向内存的一边移动,然后再清除边界以外的内存。

对jvm的老年代的对象大多不是那种“朝生夕死”的情况,标记-整理算法的移动成本反而是合算的,吞吐量上的“stop the world”是短暂的。Parallel Old垃圾收集器是基于标记-整理算法的。