JVM好好学一(垃圾回收)

JVM好好学一(垃圾回收)

大纲

  • 1、垃圾回收介绍

  • 2、垃圾回收算法

  • 3、垃圾回收器

  • 4、四种引用类型

1、垃圾回收介绍

        在JAVA体系中,垃圾回收机制指的就是对象的内存回收机制(回收目标、回收策略),这种回收是不需要程序员主动操作的,由虚拟机在后台完成。

        判断一个对象可以被回收,主要包括引用计数法和可达性分析法。

  • 引用计数法

        每个对象的对象头中都存了一个引用计数器counter,有对象引用时计数值 +1,引用被释放时计数值 -1,当计数器为 0 时就可以被JVM回收。这种方式有一个缺点是不能解决循环引用的问题。

  • 可达性分析

        可达性分析法也被称之为GCRoot根搜索法, 可达性是指,如果一个对象会被至少一个在程序中的变量通过直接或间接的方式被其他可达的对象引用,则称该对象就是可达的。 可达性分析的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的

  • GC root对象
(1) 虚拟机栈中引用的变量
(2) 方法区中类静态属性引用的对象
(3) 方法去中常量引用的对象
(4) 本地方法栈中JNI(一般说的是native方法)应用的变量

2、垃圾回收算法

2.1 标记清除

        它是最基础的收集算法,这个算法分为两个阶段,“标记”和”清除“。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它有两个不足的地方:

  1. 效率问题,标记和清除两个过程的效率都不高;

  2. 空间问题,标记清除后会产生大量不连续的碎片

2.2 复制算法

        为了解决效率问题,复制算法出现了。它可以把内存分为大小相同的两块,每次只使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块区,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收

2.3 标记整理

        根据老年代的特点提出的一种标记算法,标记过程和“标记-清除”算法一样,但是后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象向一段移动,然后直接清理掉边界以外的内存

2.4 分代回收算法

        通过前面的笔记我们知道JVM将堆划分了不同的区域:新生代、老年代、永久代,每个区域的对象生命周期是不一样的。

        分代收集算法,其实就是针对不同生命周期的对象采用不同的垃圾回收算法进行回收,以便提高回收效率。 在JVM的新生代区域内存中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用 “标记—清理” 或者 “标记—整理” 算法 来进行回收。

  • 新生代:复制算法
  • 老年代:标记-清除算法、标记-整理算法

        分代收集算法,其实是不是一个新的算法,而是对已经出现的复制算法、标记整理算法、标记清除算法进行一个灵活的整合和运用。

3、垃圾回收器

        java虚拟机规范对垃圾收集器应该如何实现没有任何规定,因为没有所谓最好的垃圾收集器出现,更不会有万金油垃圾收集器,只能是根据具体的应用场景选择合适的垃圾收集器。

3.1 Serial 收集器

        Serial 是串行收集器,有两种:Serial和Serial Old。

        串行收集器采用单线程方式进行收集,且在 GC 线程工作时,系统不允许应用线程打扰。此时,应用程序进入STW暂停状态,即 Stop-the-world。Stop-the-world 暂停时间的长短,是衡量一款收集器性能高低的重要指标。Serial 是针对新生代的垃圾回收器,采用“复制”算法。

        Serial Old 是 Serial 收集器的老年代版本,单线程收集器,采用“标记-整理”算法。这个收集器的主要意义也是在于给 Client 模式下的虚拟机使用。

3.2 ParNew 收集器

        并行收集器充分利用了多处理器的优势,采用多个 GC 线程并行收集。可想而知,多条 GC 线程执行显然比只使用一条 GC 线程执行的效率更高。 一般来说,与串行收集器相比,在多处理器环境下工作的并行收集器能够极大地缩短 Stop-the-world 时间。ParNew 是针对新生代的垃圾回收器,采用“复制”算法,可以看成是 Serial 的多线程版本

3.3 Parallel

        Parallel又分为Parallel Scavenge 收集器和Parallel Old收集器。

        Parallel Scavenge 是针对新生代的垃圾回收器,采用“复制”算法,和 ParNew 类似,但更注重吞吐率。在 ParNew 的基础上演化而来的。 Parallel Scanvenge 收集器被誉为“吞吐量优先”收集器。 Parallel Old 是 Parallel Scanvenge 收集器的老年代版本,多线程收集器,采用“标记-整理”算法。

  • 吞吐量

        吞吐量就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值,即吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。如虚拟机总运行了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是99%。

3.4 CMS收集器

        CMS(Concurrent Mark Swee)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS 收集器仅作用于老年代的收集,采用“标记-清除”算法,它的运作过程分为 4 个步骤:

1)初始标记(CMS initial mark)
暂停所有的其他线程,并记录下直接与root相连的对象

2)并发标记(CMS concurrent mark)
同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证
包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证
可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地

3)重新标记(CMS remark)
重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分
对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长

4)并发清除(CMS concurrent sweep) 
开启用户线程,同时GC线程开始对为标记的区域做清扫

CMS主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:

        对CPU资源敏感;无法处理浮动垃圾;

        它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生

3.5 G1 收集器

        G1的出现,解决了CMS带来的内存碎片问题。G1 之前的其他收集器进行收集的范围都是整个新生代或者老年代,而 G1 不再是这样。在堆的结构设计时,G1 打破了以往将收集范围固定在新生代或老年代的模式,G1 将堆分成许多相同大小的区域单元,每个单元称为 Region,Region 是一块地址连续的内存空间,G1 模块的组成如下图所示:

        堆内存会被切分成为很多个固定大小的 Region,每个是连续范围的虚拟内存。堆内存中一个 Region 的大小可以通过-XX:G1HeapRegionSize参数指定,其区间最小为 1M、最大为 32M,默认把堆内存按照 2048 份均分。

        每个 Region 被标记了 E、S、O 和 H,这些区域在逻辑上被映射为 Eden,Survivor 和老年代。存活的对象从一个区域转移(即复制或移动)到另一个区域,区域被设计为并行收集垃圾,可能会暂停所有应用线程。

        如上图所示,区域可以分配到 Eden,Survivor 和老年代。此外,还有第四种类型,被称为巨型区域(Humongous Region)。Humongous 区域是为了那些存储超过 50% 标准 Region 大小的对象而设计的,它用来专门存放巨型对象。如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC。

        G1收集过程分为四个步骤:初始标记、并发标记、最终标记、筛选回收

(1) 初始标记(Initial Marking):
标记GC Roots能直接关联到的对象。

(2) 并发标记(Concurrent Marking)
从GC Roots开始对堆中的对象进行可达性分析,找出存活的对象,这阶段耗时较长,
但可与用户程序并发执行。

(3) 最终标记(Final Marking):修正并发标记期间因用户程序继续运作而导致标记产生
变动的那一部分标记记录。

(4) 筛选回收(Live Data Counting and Evacuation):
对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。
这个阶段也可以做到与用户程序一起并发执行。

4、四种引用类型

  • 强应用(Strong Reference)
Object obj = new Object(),这类引用是 Java 程序中最普遍的。只要强引用还存在,
垃圾收集器就永远不会回收掉被引用的对象
  • 软引用(Soft Reference)
它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将
被垃圾收集器回收。JDK1.2 之后提供了SoftReference类来实现软引用
  • 弱引用(Weak Reference)
它也是用来描述非必须对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到
下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被
弱引用关联的对象。在 JDK1.2 之后,提供了WeakReference类来实现弱引用
  • 虚引用(Phantom Reference)
也称为幻引用,最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来
取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时
收到一个系统通知。JDK1.2 之后提供了PhantomReference类来实现虚引用
end
  • 作者:旭仔(联系作者)
  • 发表时间:2024-03-21 23:34
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 公众号转载:请在文末添加作者公众号二维码(公众号二维码见右边,欢迎关注)
  • 评论