Java垃圾回收算法

Posted this.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java垃圾回收算法相关的知识,希望对你有一定的参考价值。

垃圾回收目的在于回收那些无用对象占用的内存,以释放出空间,减缓程序设计的压力。既然Java实现了垃圾的自动回收,那么就有必要了解一下它是如何实现垃圾回收的。

如何判断对象“已死”?

在垃圾收集器对堆进行回收前,首先需要做的事情就是判断哪些对象已经不会再被使用。主要有两种方式实现判定:

引用计数算法

给每个对象添加一个引用计数器(初始值为1),每当有一个地方引用它的时候,计数器就加一。每当引用失效时,计数器就减一。任何时候当计数器为0的时候,就代表着这个对象不能再被引用。
引用计数算法的判定效率高,但是却很难解决对象相互引用的问题。

根搜索算法

从一系列名为“GC ROOT”的对象为起点,开始向下搜索,搜索的路径称为引用链。当一个对象到GC ROOT没有任何引用链,则说明此对象是不可用的。
在Java语言里,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI(Native方法)的引用对象。

可以通过下图来理解:

垃圾回收算法

既然已经可以判定哪些对象已经死亡,那么就要对这些对象所占用的空间进行回收。垃圾回收主要有以下算法:

标记-清除算法

此算法主要分两个阶段:标记阶段和清除阶段。首先,标记那些需要被回收的对象,然后统计回收掉这些被标记的对象。如下图所示:

其实,不难发现有个问题,就是当标记的对象被回收后,内存中产生了大量的内存碎片。如果碎片过多,会导致下次有一个占用内存空间较大的对象需要分配,却无法找到足够的连续空间的问题出现,进而有提前了下一次GC操作。

复制算法

这个算法的主要思想是将内存平均范围分为两块,每次只用其中一块。当一块内存用完了,就像其中存活的对象移动到另一块,然后清理已使用过的内存。这样就可以避免产生内存碎片,但同时代价也高,因为可用的内存仅仅变成了总内存的一半。
复制算法示意图:

标记-整理算法

标记整理算法的主要思想是:首先标记内存中需要回收的对象,这点与标记-清除算法一样,然后让存活的内存都向一边移动,用完成后,清理里掉存活内存边界外的内存。

分代收集算法

很多虚拟机都采用此算法。根据对象的存活周期把堆分为新生代和老年代。由于新生代中经常会有大量对象死亡,所以采用了复制算法,仅仅需要复制少量的存活对象。而老年代存活率高,占用空间大,就需要使用标记-清除或是标记-整理算法来收集。

垃圾收集器

有了回收垃圾的思想,那必然需要垃圾收集器来去实现。垃圾收集器主要有以下类型:

Serial/Serial Old收集器

Serial是一个针对新生代的单线程收集器。当执行垃圾回收时会停止所有工作线程,采用复制算法。而Serial Old则是针对老年代的收集器,采用标记-整理。此收集器简单高效,但却会发生停顿现象。

ParNew收集器

与Serial收集器相比没有太多变化,但该收集器是多线程版本,实现了垃圾收集的并发操作。即用户线程可以和垃圾回收的线程基本上并发进行。

Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代的多线程收集器,它在回收期间不需要暂停其他用户线程,其采用的是复制算法。该收集器主要是为了达到一个可控的吞吐量。

Parallel Old

Parallel Old是Parallel Scavenge收集器的老年代版本,使用标记-整理算法。

CMC收集器

该收集器是一种以获取最短回收停顿时间为目标的收集器。采用的是标记-清除算法。其主要步骤为初始标记、并发标记、重新标记、并发清除。

G1收集器

G1收集器采用标记-整理算法来实现垃圾回收,不会产生内存碎片。同时能非常精确地控制停顿。

内存分配策略

示意图:

堆中把内存分为Eden区和Survivor区。大多数情况下对象会在新生代Eden区和Survivor的From区分配。当Eden区没有足够空间时,则会发生一次Minor GC。GC后,如果有足够空间存放下一个对象,仍然会存放在Eden区和Survivor的From区。GC时,会将Eden区和From区的存活对象转移到To区,然后清理Eden和From区。如果在转移时To区不够空间存放对象,这个对象就会被移动到老年代中去。在GC完成后,使用的便是Eden区和To区,下次垃圾回收就会将存活对象移动到From区中,循环往复。
每当一个对象在Survivor区中躲过一次GC,年龄就加一。当年龄到达15时,就要被移到老年代中去。当然,并不是一定要年龄到达15才会被移到老年代中,如果Survivor区中相同年龄的所有对象大小总和超过Survivor容量的一半,年龄大于等于这个界限年龄的对象就会直接进入老年代。
此外大对象也会直接进入老年代。如: byte[] bs= new byte[4*1024*1024];

关于Minor GC与Full FC

  • Minor GC:新生代垃圾收集,由于新生代经常会有大量对象死亡,所以Minor GC发生频繁。
  • Full FC:老年代垃圾收集。经常会至少伴随一个Minor GC。速度较Minor GC慢。

以上是关于Java垃圾回收算法的主要内容,如果未能解决你的问题,请参考以下文章

《深入理解JAVA虚拟机》垃圾回收时为什么会停顿

读Java性能权威指南(第2版)笔记16_垃圾回收C

java GC垃圾回收机制G1CMS

垃圾回收-G1算法

垃圾回收器为什么必须要停顿下?

垃圾回收算法的前世今生(转)