GC 玩起来(一)基础知识扫盲

202 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

摘要
GC 日志是判断 Java 应用程序内存是否存在故障的重要判断依据。《GC 玩起来》这个小系列,期望能够使零基础的读者快速理解 GC 相关的重要概念,最终掌握 GC 日志的分析方法。
第一篇《基础知识扫盲》旨在梳理 GC 相关的重要的基础知识,对其做简要、易懂的介绍。

1 堆的内存模型

Heap(堆)是存储对象实例的区域,是 JVM 所管理的内存中的最大的一块。堆的内存模型如下图:

1.1 新生代

Young Generation(新生代,也称年轻代),一般占堆的 1/3 空间。

绝大多数新创建的对象都放在新生代中,换句话说,新生代是绝大多数对象的诞生地(特例放在后面做说明总结)。

新生代自身又分成一个 Eden 区和两个 Survivor 区(一般情况下, Eden 区与两个 Survivor 区的大小比例为 8:1:1 ),这三个区的作用与垃圾回收的机制关系密切,放在后面做详细说明。

1.2 老年代

Old Generation(老年代),一般占堆的 2/3 空间。

老年代主要存放生命周期比较长的对象。

1.3 持久代与元空间

往往还会有一个持久代的概念,同新生代、老年代一同讨论。这是因为从逻辑角度出发,持久代属于堆来管理(但在物理存储上,持久代并不属于堆)。

Permanent Generation(持久代,也称永久代),其实就是 JVM 内存模型中的 Method Area(方法区),主要用来存放如 Class 、 Method 这样的元数据。

那么元空间是什么呢?

Meta Space(元空间)从 JDK1.8 起,替代掉了原有的持久代。

元空间与持久代最大的区别就是,元空间使用本地内存,所占用的空间不在 JVM 内部。

1.4 内存泄露与内存溢出

内存泄露,指的是对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。

内存溢出,指的是没有足够的内存空间,来创建新的对象。内存泄露到一定程度就会导致内存溢出。

2 垃圾回收基本理论

2.1 GC 相关概念

GC(Garbage Collection),即“垃圾回收”,垃圾回收机制主要管理的区域就是堆。

没有任何引用指向的对象(即对象“不可达”),会被 JVM 视为垃圾而回收。

Java 的垃圾回收机制能够使 JVM 根据一定的回收策略自动回收内存,使得 JVM 时刻具有可用的内存空间。

垃圾回收会造成应用程序的暂停,也就是所谓的 stop-the-word(stw)。

2.2 GC 分类

HotSpot VM 是 Sun / Oracle JDK 和 OpenJDK 的默认虚拟机,在业界具有绝对的霸主地位,被广泛使用。

HotSpot VM 对于 GC 的分类为:

  • Partial GC:部分收集模式,分为以下三种:

    • Young GC:只收集新生代;

    • Old GC:只收集老年代;

    • Mixed GC:收集全部新生代及部分老年代(仅 G1 垃圾回收器存在这种模式)。

  • Full GC:收集整个堆,包括新生代、老年代、持久代(JDK1.8 之前)。

还有两种常见的俗称:

  • Minor GC ,等同于 Young GC 。

  • Major GC ,通常等同于 Full GC ,有时也可以指 Old GC 。这种混乱是 JVM 长久以来都没有规范这些名词而造成的。

2.3 Young GC 的过程

下面来看 Young GC 的过程,同时来理解新生代的 Eden 区和两个 Survivor 区(为区别,我们分别称两个区为 From 区与 To 区)的作用:

(1)Java 新生对象会被创建在新生代中的 Eden 区(如果是大对象,则会直接创建在老年代),当 Eden 区空间不足时,就会触发一次 Young GC ,回收新生代的垃圾对象;

(2)回收垃圾对象后, Eden 区与 SurvivorFrom 区中存活的对象年龄加 1 ,到达老年代年龄标准(一般是 15)的对象会被复制到老年代,其他未到达老年代年龄标准的存活对象,则复制到 SurvivorTo 区(当 SurvivorTo 区空间不足时,就复制到老年代);

(3)清空 Eden 区与 SurvivorFrom 区;

(4)SurvivorFrom 区与 SurvivorTo 区互换,原 SurvivorFrom 区作为下一次的 SurvivorTo 区,原 SurvivorTo 区作为下一次的 SurvivorFrom 区。

上述过程描述的又称为“分代收集算法”,目前主流的 JVM 大多采用这种算法。

Young GC 会十分频繁的进行,但是其回收速度是很快的(也就是 stw 时间很短暂)。

2.4 对象进入老年代的条件

我们来总结一下对象进入到老年代的条件,有以下几种情况:

  • 需要较大存储空间的对象,在创建时会直接进入老年代(避免在新生代中的复制操作);

  • 对象年龄到达老年代年龄标准时,对象进入到老年代(每进行一次 Young GC,对象年龄就增加 1);

  • Eden 区对象复制到 SurvivorTo 区时,若 SurvivorTo 区空间满了,对象会进入到老年代;

  • 当 SurvivorTo 区中存在某个年龄的对象大小总和大于 SurvivorTo 空间的一半时,则该年龄的对象会进入到老年代。

2.5 Full GC 的触发条件

在应用程序运行十分健康的情况下, Full GC 不会频繁的被触发,换句话说,出现频繁 Full GC 的时候也就代表着应用程序内存存在故障了。 Full GC 的 stw 时间会是 Young GC 的十倍以上,频繁的 Full GC 会造成应用的明显卡顿。

以下几种情况会触发 Full GC :

  • 调用 System.gc() 方法(强烈不建议调用,内存管理的工作应该让 JVM 自己去做);

  • 老年代放置对象时空间不足;

  • 老年代连续的内存空间不足(JVM 判断老年代没有足够的持续的空间来放置大对象);

  • Young GC 需要老年代的内存空间做担保,当空间分配担保失败时,会触发 Full GC ;

  • 持久代(JDK1.8 之前)或元空间(JDK1.8 及以后)空间不足。


以上就是我们在分析 GC 日志前,所应了解的 GC 相关的重要的基础知识,下一篇文章将着重介绍 JVM 的垃圾回收器。