持续创作,加速成长!这是我参与「掘金日新计划 · 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 的垃圾回收器。