Java垃圾回收机制(Garbage Collection)

最后一次更新时间:Friday, August 14th 2020, PM

 

垃圾回收是Java特有的功能,即由JVM主动判断回收那些程序不再需要的“垃圾”信息。

 

0 前记

Java GC主要是对于堆内存来说的。此小节下面的部分,都是基于堆内存进行解释。所以,在这里我们先来看一下除了堆内存,其它地方(即方法区的永久代PermGen)是如何GC的。这一部分相对独立,且重要性偏小,为了不影响大家的整体理解,就单独放在这里好了。

0.1 方法区GC

如何判断一个类无用:
需要满足三点
1.所有实例都已经被回收(堆中无实例)。
2.加载该类的ClassLoader已经被回收。
3.该类对应的对象无引用(即无法通过反射访问该类的信息)。

0.2 常量池GC

如何判断一个常量可被GC:
没有引用的常量就是废弃常量,可被GC。

 

 

 

1 判断对象是否存活の算法

在发生GC前,要判断对象是否死亡,这里用到的判断方法有:

1.1 引用计数法:

给对象添加一个计数器,每有一个地方引用它,计数器值+1;引用失效,计数器值-1。当计数器值==0时,对象死亡,不可能再被引用。
Java不使用该方法,其存在问题:A与B相互引用时,A和B的计数器值恒大于等于1,永远无法被回收。

1.2 可达性分析算法:

首先通过一系列名为GC Roots的对象作为起点,从GC Roots向下搜索,走过的路径称为引用链(Reference Chain)。若待判断的对象与GC Roots之间不可达(无 Reference Chain),则为垃圾。

可作为GC Roots的有:
1.虚拟机栈中引用的对象
2.本地方法栈中引用的对象
3.方法区常量引用的对象
4.方法区类静态属性引用的对象

如下图所示,Object 03、04、05 与 GC Roots 之间无 Reference Chain,故会被GC。而Object 01、02 会继续存活。

 

 

 

2 四种引用

除了上述的两种算法外,Java GC还会通过引用的类型,来判断是否回收当前的对象。Java中的引用常分为如下四种类型:

  1. 强引用:=,new就是强引用。GC Roots可达,不会被回收。
  2. 软引用:有用但非必须。OS在将要OutOfMemory之前,GC才会回收当前对象。
  3. 弱引用:OS无论Memory是否充足,只要GC,一定会回收当前对象。ThreadLocal就是弱引用。
  4. 虚引用:主要用来跟踪对象被垃圾回收的活动。

 

 

 

3 对象的访问定位(对象的引用)

要GC某对象,首先要找到它。这里也是有两种算法可供了解:

  1. 句柄法
  2. 直接指针

这两种方法都是通过栈中的引用(reference),来操作堆上的对象。

3.1 句柄法

从堆内存中,划分出一块区域作为句柄池。

reference中存放 对象的句柄地址
句柄中存放的是对象的 实例数据 与 类型数据 各自的地址

句柄法优点:对象被移动时,只需修改 实例数据的指针,reference无需修改。

3.2 直接指针

HotSpot(全称为Java HotSpot Virtual Machine。一种较新版本的JVM,用来代替JIT,性能较高。是Sun JDK 和 Open JDK中自带的VM)使用该方法访问对象。

reference中存放 对象的地址
对象中存放 实例数据 和 类型数据的地址

直接指针优点:速度快,节省了一次指针定位的时间。

 

 

 

4 垃圾回收の算法

对象的存亡判断,访问定位等问题已经解决了。万事俱备,亟待回收,接下来就该重头戏——GC算法登场!

4.1 标记-清除算法

先标记,再清除。最基本的GC算法,其它算法都是由它改进而来。有两个缺点:

  1. 效率低下
  2. 空间问题(碎片化:清理后的内存空间零零散散,不连续)

4.2 复制算法

基于 4.1 标记-清除算法,一定程度的解决了上述两个问题。

它将内存分为大小相同的两块,每次使用其中一块。当该块内存满了便触发GC,将存活的对象整理到另外一半内存中,再将刚刚使用的内存完全擦除。下图可以强化理解:

4.3 标记-整理算法

同上,基于 4.1 标记-清除算法,一定程度的解决了上述两个问题。

标记之后,清除掉死亡的对象,再将存活的对象连续放在一起。

4.4 分代垃圾回收算法

目前的JVM都在用这个,综合了上述各算法,更好的提升了GC效率。

Eden和Survivor一起使用复制算法,Tenured(老年代)使用标记-整理算法

4.4.1 划分

堆内存分为 年轻代(Young Generation)老年代(Old Generation)
年轻代又分为 Eden区Survivor区
Survivor区又分为 From区To区(HotSpot官方说法,民间常用的说法有分为S0/S1区,S1/S2区,都是一个意思)。

4.4.2 Minor GC

对于年轻代的垃圾回收,有一个专属的名词“Minor GC”

触发条件:当新对象生成,且在Eden区分配内存失败时,触发Minor GC。

在整个年轻代中,每个对象都有一个“年龄”。每触发一次Minor GC,年龄+1,且会被挪动(拷贝)到另外一个Survivor区域。

举个例子,假如,我们使用了Eden区和From Survivor区,那么To Survivor区就是空的。此时触发一次Minor GC,所有对象年龄+1。标记清除掉无引用的垃圾对象。再进行一次判断,若对象的年龄达到了15,则将其送入老年代。剩下的对象(存活,且年龄介于0和15之间),送入To Survivor区,此时From Survivor区就变成空的了。等到下一次触发Minor GC时,则再挪入From Survivor区中,空出To Survivor区,以此循环往复。

4.4.3 Full GC

对于老年代的垃圾回收,也有”一个”专属的名词“Full GC”(也有称为Major GC的 =_= )。

触发条件:Full GC 通常由当前JVM的垃圾收集器来决定。

每一次Full GC 通常都会触发至少一次 Minor GC

 

 

 

5 常见的垃圾收集器

在 4.4.3 中提到了在何种情况下,会使用到垃圾收集器。


除特别声明外,本站所有文章均采用 CC BY-SA 4.0 协议 ,转载请注明出处!