JVM原理(三):垃圾回收

一、JVM内存是如何进行分配和回收的

Java的自动内存管理机制就要是针对对象内存的分配和回收,同时Java内存管理主要是针对堆内存中对象的分配与回收
JVM原理(三):垃圾回收_第1张图片
引用我上一篇博客的堆内存中的对象内存分配和回收和流程—堆内存划分及垃圾回收时的内存分配:https://blog.csdn.net/by_yanzhenshun/article/details/104327550

在创建对象的时候,实例对象基本都会被分配在Eden区,Survivor区是空的。

(1)在进行第一次GC时,当新生代的 Eden Space 就会发生一次Minor GC,幸存下来的对象被复制到s0区,Eden 区都会被清理掉。

(2)在进行下一次GC的时候,触发时机是 Eden Space 和s0内存不足。与(1)不同的是,此次幸存的下来的对象,以及把上次 GC移到 S0 中的对象会被复制到s1区,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1)。这时,Eden区和s0都会被清理掉。

(3)当s0无法足够存储某个对象,则将这个对象存储到老年代。

(4)当年龄增加到一定程度(默认15,可以通过参数 -XX:MaxTenuringThreshold 配置),这个对象就从年轻代转移到了老年代。

这样,老年代中的对象就持续增加。
(5)触发 major gc 对老年代空间进行清理和压缩。

Minor GC 和Major GCd的区别:
  • 新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
  • 老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
为什么比较大的对象会直接进入老年代

大对象就是需要大量连续内存空间的对象(比如:字符串、数组),为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。

二、对象的死亡

堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。
JVM原理(三):垃圾回收_第2张图片

2.1 引用计数法

在java中,如果想操作一个对象就必须使用引用进行,可以每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的。

优点:实现简单、高效
缺点:无法解决对象间相互循环引用的问题,所以计数器一直不为0,无法通知JVM执行GC

2.2 可达性分析

通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记
过程。两次标记后仍然是可回收对象,则将面临回收

JVM原理(三):垃圾回收_第3张图片

2.3引用类型

在上面的引用计数器和可达性分析法,都用了都用了引用
引用的类型有四种
(一)强引用(StrongReference)
在java中最常见的就是常引用,如果一个对象拥有强引用时,那就类似于不可缺少的生活用品,他是永远可达的,所以他是不可能被垃圾回收的,当内存不足时,JVM会报出OutofMemmoryExeception,,使程序异常终止。

(二)软引用(SoftReference)
如果一个对象只拥有软件用,那就类似于可有可无的生活用品,当内存足够时,它不会被垃圾回收;当内存不足时,它会被回收。软引用通常用在对内存敏感的程序中。

(三)弱引用(WeakReference)
如果一个对象只拥有弱件用,那就类似于可有可无的生活用品,与软引用不同的是,不管内存是够足够,它都会被回收。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

(四)虚引用
虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
虚引用的主要作用是跟踪对象被垃圾回收的状态。

2.4 判断一个类是无用类

需要满足三个条件

  • 该类的所有实例都已经被回收
  • 加载该类的类加载器classloader已经被回收
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

三、垃圾回收算法

3.1 标记 - 清除算法

分为两个步骤:标记和清除。首先会标记那些对象该被回收,标记完成后统一将对象进行回收。它是最基础的垃圾回收算法,存在两个问题:
(1)效率问题
(2)空间问题,内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题
JVM原理(三):垃圾回收_第4张图片

3.2 复制算法

将内存分成大小相同的两块。每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
JVM原理(三):垃圾回收_第5张图片
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原
本的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

3.3 标记整理算法

结合了以上两个算法,为了避免缺陷而提出。标记阶段和 Mark-Sweep 算法相同,标记后不是清
理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图
JVM原理(三):垃圾回收_第6张图片
三种算法的比较:

  效率: 复制 > 标记整理 > 标记清除  (此处的效率只是简单的对比时间复杂度,实际情况不一定如此)

  内存利用率: 标记整理 > 标记清除 > 复制

  内存整齐度: 复制 = 标记整理 > 标记清除
3.4分代收集算法

分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存
划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young
Generation)。
老年代的特点是每次垃圾回收时有大量对象死去,只有少量存活,那就选用“复制算法”,只需要付出少量存活对象的复制成本就可以完成收集。,
新生代的特点是每次垃圾回收时老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。

收集流程也就是文章开头的Java内存管理机制。

四、垃圾收集器

Java 堆内存被划分为新生代和年老代两部分,新生代主要使用复制;年老代主要使用标记-整理垃圾回收算法和标记-清除垃圾回收算法,因此 java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:
JVM原理(三):垃圾回收_第7张图片

4.1 serial垃圾收集器(单线程、复制算法、最基本)
  • Serial(英文连续)是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾
    收集器。
  • Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工
    作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。
  • Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限
    定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此 Serial
    垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。
    JVM原理(三):垃圾回收_第8张图片

4.2 ParNew 垃圾收集器(Serial+多线程)

ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃
圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也
要暂停所有其他的工作线程。
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
JVM原理(三):垃圾回收_第9张图片

4.3 . Parallel Scavenge 收集器(多线程复制算法、高效)

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在困难的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。
JVM原理(三):垃圾回收_第10张图片

4.4.Serial Old 收集器(单线程标记整理算法 )

Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

4.5 Parallel Old 收集器(多线程标记整理算法)

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

4.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。

CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  • 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
  • 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  • 并发清除: 开启用户线程,同时 GC 线程开始对为标记的区域做清扫。
    JVM原理(三):垃圾回收_第11张图片

从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:

(1)对 CPU 资源敏感;
(2)无法处理浮动垃圾;
(3)它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

4.7 G1 收集器
  • G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
  • Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与 CMS 收集器,G1 收 集器两个最突出的改进是:
  1. 基于标记-整理算法,不产生内存碎片。
  2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
  • G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域
    的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾
    最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收
    集效率

你可能感兴趣的:(java基础,jvm,java,编程语言)