JVM 垃圾回收

JVM 垃圾回收

Guide哥文章

日期:2021年3月20日11:14:31

常见面试问题

  1. 如何判断对象是否死亡
  2. 简单介绍一下强、软、弱、虚引用区别
  3. 如何判断一个常量是废弃常量
  4. 如何判断一个类是无用的类
  5. 垃圾收集有哪些算法,各自特点是什么?
  6. HotSpot 为什么要使用分代?
  7. 常见的垃圾回收器有哪些?
  8. 介绍一下 CMS、G1 收集器
  9. Minor GC 和 Full FC 关系

...

入门介绍

Java 的自动内存管理主要是针对对象内存的回收和分配。

其中,Java 堆是垃圾收集器管理的主要区域,因此Java 堆也被称为 GC 堆(Garbage Collected Heap)。

目前主流垃圾收集器基本都是采用分代来及收集算法,Java 堆还可以细分为:新生代(Young)和老年代(Old)

进一步划分的目的是更高效的回收内存和更快的分配内存

堆空间结构

image-20210320093842591

通常情况下,对象首先在 Eden 区域分配空间,在一次 Minor GC 后,如果对象还存活,则会进入 s0 或者 s1,同时,对象的年龄会+1(从Eden区 -> Survivor区后对象的年龄变为 1)

当对象的年龄增加到一定程度(默认为15,可以通过 -XX:MaxTenuringThreshold 设置),就会被移至老年代中。

经过一次 Minor GC 后,Eden 区和 From 区被清空。这是 From 和 To 会交换角色

堆内存常见分配策略

对象优先在 Eden 区分配

package jvm;

/**
 * @description: 对象优先在 Eden 区分配空间实例
 * JVM参数:-XX:+PrintGCDetails -Xms100m -Xmx100m
 * @author: WuDG/1490727316@qq.com
 * @date: 2021/3/20 9:46
 */
public class EdenInitDemo {
    byte[] eden = new byte[1024*1024*15];
    public static void main(String[] args) {
        EdenInitDemo demo = new EdenInitDemo();
    }
}

输出

Heap
 PSYoungGen      total 29696K, used 18452K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
  eden space 25600K, 72% used [0x00000000fdf00000,0x00000000ff105130,0x00000000ff800000)
  from space 4096K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x0000000100000000)
  to   space 4096K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffc00000)
 ParOldGen       total 68608K, used 0K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
  object space 68608K, 0% used [0x00000000f9c00000,0x00000000f9c00000,0x00000000fdf00000)
 Metaspace       used 3247K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K

可以看出,Eden 区基本被分配完全。

加入把对象中的 byte 数组增大

byte[] eden = new byte[1024*1024*30];
Heap
 PSYoungGen      total 29696K, used 3612K [0x00000000fdf00000, 0x0000000100000000, 0x0000000100000000)
  eden space 25600K, 14% used [0x00000000fdf00000,0x00000000fe287240,0x00000000ff800000)
  from space 4096K, 0% used [0x00000000ffc00000,0x00000000ffc00000,0x0000000100000000)
  to   space 4096K, 0% used [0x00000000ff800000,0x00000000ff800000,0x00000000ffc00000)
 ParOldGen       total 68608K, used 30720K [0x00000000f9c00000, 0x00000000fdf00000, 0x00000000fdf00000)
  object space 68608K, 44% used [0x00000000f9c00000,0x00000000fba00010,0x00000000fdf00000)
 Metaspace       used 3247K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K

此时,由于 Eden 区内存以及分配完,而 Survivor 空间又不足,因此大对象提前转移到老年代中

大对象直接进入老年代

大对象就是需要大量连续内存空间的对象(如:数组)

长期存活的对象进入老年代

多个 Minor GC 仍存活的对象。对象年龄增加到一定程度(默认为15)就会被晋升到老年代中

动态对象年龄判断

X = 某个年龄 X > Survivor 区/2

MaxTenuringThreshold = min(X,15)

总结

  • 部分收集(Partial GC)

    • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集
    • 老年代收集(Major GC / Old GC):只对老年代进行垃圾收集
    • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集
  • 整堆收集(Full GC):收集整个 Java 堆 和方法区

    判断对象是否死亡

引用计数器

给对象添加一个引用计数器,每当有一个地方引用它,计数器就+1,当引用失效,计数器就 -1。任何时候计数器为 0 的对象判断为死亡。

特点:

  • 效率高
  • 简单

缺点:

  • 无法解决对象直接循环引用问题

可达性分析

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

可作为 GC Roots 的对象

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

4种引用类型

传统定义:如果 reference 类型的数据存储的值代表的是另一块内存的起始地址,就称这块内存为一个引用。

JDK1.2后,Java 对象引用的概念进行了扩充,将引用分为强、软、弱、虚四种。

强引用(Strong Reference)

这是最普遍的引用类型。如 使用 new 关键字创建的对象引用就是强引用。垃圾回收期不会回收强引用类型的对象,当内存空间不足时,JVM 会抛出 OOM错误。

软引用(Soft Reference)

对象指向软引用。如果内存空间足够,则垃圾回收期就不会回收,如果内存空间不足,则会回收这部分对象。

软引用可以用来实现内存敏感的高速缓存。

弱引用(Weak Reference)

不管当前内存空间是否足够,当触发 GC 时,这部分的对象空间都会被回收。

虚引用(Phantom Reference)

虚引用并不会决定对象的生命周期。

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都看被来及收集器回收。

虚引用主要用来跟踪对象被垃圾回收的活动

注意:

在程序设计中,一般很少使用弱引用虚引用

软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出等问题

不可达的对象并非“非死不可”

在可达性分析中不可达的对象,要经历两次标记过程才可能真正死亡。

第一次标记:判断此对象是否有必要执行 finalize 方法

第二次标记:判断对象是否 重写 finalize方法或finalize方法是否已经被虚拟机调用过

判断一个常量是否是废弃常量

运行时常量池主要回收的是废弃的常量。

如何判断一个对象是否已经废弃?

如果当前没有任何 String 对象引用该字符串常量的话,就说明该常量就是废弃常量,如果这是发生内存回收且判断为有必要,该废弃常量就会被系统清理出常量池

如何判断一个类是无用的类

方法区主要回收的是无用的类

  • 该类的实例都已经被回收,JVM中不存在该类的实例
  • 加载该类的类加载器(ClassLoader)已经被回收
  • 该类对象的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集算法

  • 标记-清除算法
  • 标记-复制算法
  • 标记-整理算法
  • 分代收集算法

标记-清除算法

此算法分为“标记”和“清除”两个结算

  • 标记:标记处所有不需要回收的对象
  • 清除:清除全部没有被标记的对象

缺点:

  • 空间问题(产生大量不连续碎片)

标记-复制算法

为了解决效率问题

将内存分为大小相同的两块空间,每次只使用其中的一块。当其中一块内存空间使用完后,就将还存活的对象复制到另一块,如何再把使用的空间全部清理

标记-整理算法

针对老年代特点提出的一种垃圾清除算法

类似“标记-清除”算法

标记后,让所有存活对象向一端移动,如何直接清理边界以外的内存

特点

  • 不会产生空间碎片

分代收集算法

可以根据不同区的特点来选择合适的垃圾收集算法

如在新生代中,每次 GC 都会有大量的对象死亡,所以可以选择“标记-复制”算法,只需复制少了存活的对象就可以完成垃圾收集

老年代中对象存活几率高,因此可以选择“标记-清除”或者“标记-整理”算法

垃圾收集器

垃圾收集算法是内存回收的方法论

垃圾收集器是内存回收的具体实现

垃圾收集器种类很多,没有最好,只有最适合的

Serial 收集器

串行,单线程完成垃圾收集工作,在进行垃圾收集工作时必须暂停其他所有工作线程(STOP THE WORLD)

新生代采用标记-复制算法,老年代采用标记-整理算法

特点:

  • 简单
  • 高效:没有线程切换开销

ParNew 收集器

即 Serial 收集器的多线程版本。使用多线程进行垃圾收集

新生代采用标记-复制算法,老年代采用标记-整理算法

除了 Serial 收集器外,只有 ParNew 收集器能与 CMS收集器配合工作

Parallel Scavenge 收集器

使用标记-复制算法的所线程收集器

和 ParNew 几乎一样

Parallel Scavenge 收集器关注的是吞吐量(CPU效率)

CMS 收集器关注的是用户线程停顿时间(提高用户体验)

JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old

Serial Old 收集器

Serial 收集器的老年代版本

作用:

  • 在JDK1.5 以及以前的版本中与Parallel Scavenge 收集器搭配使用
  • 作为 CMS收集器的后备方案

Parallel Old 收集器

Parallele Scavenge 收集器的老年代版本

使用多线程和“标记-整理算法”

CMS 收集器

CMS 收集器是一种以获取最短停顿时间为目标的收集器。非常注重用户体验。

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

CMS垃圾收集步骤

  • 初始标记:暂停所有其他线程,记录下直接与root相连的对象
  • 并发标记:同时开启 GC 和用户线程,记录可达对象。由于用户线程也在同时更新引用,所以不报账这部分对象都是可达对象。因此这个算法会跟踪并记录这些发生引用更新的对象
  • 重新标记:为了修改并发标记期间因为用户程序运行而导致标记产生变动的那部分对象。T(并发标记) >> T(重新标记) > T(初始标记)
  • 并发清除:开启用户线程,同时 GC 线程开始对未标记的区域清除

缺点:

  • 对 CPU 资源名
  • 无法处理浮动垃圾
  • 产生大量空间碎片

G1 收集器

Garbage-First

是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器

同时兼顾 GC 停顿时间和吞吐量(CPU效率)

特点:

  • 并行与并发:G1能充分利用 CPU,缩短 STW 停顿时间
  • 分代收集:
  • 空间整合:
  • 可预测的停顿

G1 收集器运行步骤

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region

保证了 G1 收集器在有限的时间内尽可能高的收集效率

ZGC 收集器

与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法

# jvm 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×