JVM - GC 概念


前言

了解 JVM GC 前需要知道的。


判断对象可回收方法

GC 主要发生在运行时数据区里的堆和方法区,但主要关注的区域还是堆。那么什么是垃圾对象呢?简单的说就是某个线程在堆上创建对象使用后不再需要,则可认为该对象就是垃圾对象。

引用计数法

引用计数法就是在 JVM 层面为对象添加一个引用计数器,每当有一个地方引用它,计数器值 +1;当引用失效时,计数器值 -1;任何时刻计数器值为 0 时则表明该对象不再需要。

大多数情况这个方法是可以发挥作用的,但在循环引用的情况下就不行了:

public class Student {
    // friend 字段
    public Student friend = null;
  
    public static void test() {
        Student a = new Student();
        Student b = new Student();
        a.friend = b;
        b.friend = a;
        a = null;
        b = null;
        // gc
    }
}

上面代码中的 a 和 b 虽然被赋值为 null,但堆里对象的 friend 字段相互引用,而且没有地方能再对他们修改,这样计数器就永远不会为 0,垃圾收集器将无法回收他们。

  • 优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
  • 缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为 0。

可达性分析算法

可达性分析算法也叫根搜索算法,JVM 使用的就是该算法。

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

要进行可达性分析就需要先枚举根节点 (GC Roots),在枚举根节点过程中,为防止对象的引用关系发生变化,需要暂停所有用户线程 (垃圾收集之外的线程),这种暂停全部用户线程的行为被称为 Stop The World

  • 优点:解决引用计数法的问题
  • 缺点:STW

基础名词

JVM 是以可达性分析算法查找垃圾对象,下面名词都是以此为前提。

读/写屏障(Load/Store Barrier)

// 读屏障
public Field getField() {
	// 返回数据时先进行处理
	loadBarrier(this.field);
	return this.field;
}

// 写屏障,分为写前和写后
public void setField(Field field) {
	// 写前
	beforeStoreBarrier(field);
	this.field = field;
	afterStoreBarrier(field);
}

有点类似于 AOP。

根节点(GC Roots)

当前 GC 不会回收的对象。
Heap
GC Roots 主要来自上图中的发出箭头的对象:

  1. 在虚拟机栈 (栈帧中的本地变量表) 中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  2. 在方法区中类静态属性引用的对象,比如 Java 类的引用类型静态变量。
  3. 在本地方法栈中 JNI (即通常所说的 Native 方法) 引用的对象。

三色标记法

三色标记法是为了减少 GC 时发生的 STW(Stop The World) 时间。三色标记法将对象分为三种颜色,黑色、灰色和白色。

  1. GC 开始时,所有待处理的对象都是白色;
  2. 从 GC Roots 开始遍历对象,对象所有引用都遍历后标记为黑色,还未遍历完的对象标记为灰色;
  3. 标记完成后,对象就只有黑色和白色,白色就是要清除的垃圾数据,因为应用代码无法再操作这些对象。

当 GC 线程与应用线程并发执行时,那就需要读/写屏障帮忙,防止对象漏标导致需要的对象被清除。

安全点 Safepoint

当需要 GC 时,应用线程需要执行到某个位置才能停止,这个位置需要记录该线程的调用栈、寄存器等一些重要信息,否则等 GC 完无法重新执行该线程,而这个位置就是安全点。


算法关注点

GC 算法主要关注两点,是否高吞吐量,是否低延迟。

吞吐量

吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值。

吞 吐 量 = 用 户 代 码 运 行 时 间 ( 用 户 代 码 运 行 时 间 + G C 时 间 ) 吞吐量 = \frac{用户代码运行时间}{(用户代码运行时间 + GC 时间)} =(+GC)

虚拟机总共运行了 100 秒钟,其中垃圾收集花掉 1 秒钟,那吞吐量就是 99%。

延迟

延迟就是当发生 GC 时,应用线程被暂停的时间。


收集算法

标记-清除算法

从 GC Roots 进行扫描,对存活对象进行标记,完成后遍历堆,把未被标记的对象回收。

缺点:

  1. 若堆较大,则遍历时间长;
  2. 对象清除后,留下的空缺位置造成内存不连续。

标记-整理算法

从 GC Roots 进行扫描,对存活对象进行标记,完成后移动存活对象,并按内存地址次序依次排列。

缺点:

  1. 需要移动存活对象

标记-复制算法

复制算法简单来说就是把内存一分为二,但只使用其中一份,在垃圾回收时,将正在使用的那份内存中存活的对象复制到另一份空白的内存中,最后将正在使用的内存空间的对象清除,完成垃圾回收。

缺点:内存缩小为原来的一半

分代算法

首先这不是一种新算法,它是一种思想。现在使用的Java虚拟机并不是只是使用一种内存回收机制,而是分代收集的算法。就是将内存根据对象存活的周期划分为几块。一般是把堆分为新生代、和老年代。短命对象存放在新生代中,长命对象放在老年代中。

对于不同的代,采用不同的收集算法:

  • 新生代:由于存活的对象相对比较少,因此可以采用复制算法, 该算法效率比较快。
  • 老年代:由于存活的对象比较多哈,可以采用标记-清除算法或是标记-整理算法。

资源

JVM - 架构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值