天天看点

Java GC垃圾回收机制一、Java GC二、GC 回收的对象三、GC 回收过程

文章目录

  • 一、Java GC
  • 二、GC 回收的对象
  • 三、GC 回收过程
    • 1、图解分代内存
      • 1.1 年轻代
      • 1.2 年老代
      • 1.3 永久代
    • 2、图解 GC 回收过程
      • 对象分配策略
    • 3、Minor GC 和 Full GC

本文内容基于目前使用最广泛的 HotSpot JVM。

参考书籍:《深入理解Java虚拟机:JVM高级特性与最佳实践》–周志明

相关文章:

JVM内存模型

一、Java GC

Java 垃圾回收机制是由 GC(Garbage Collection)垃圾收集器实现的,它是 JVM 提供的一种自动内存管理和垃圾清扫机制,在空闲时间、不定时回收、无任何对象引用的对象所占据的内存空间,从而不容易出现内存泄露和内存溢出问题,一般简称「Java GC」或「JVM GC」。

  • 垃圾:无任何对象引用的对象;
  • 回收:清理 ‘垃圾’ 占用的内存空间,而非对象本身;
  • 发生时间:在程序空闲时不定时回收;
  • 发生地点:一般发生在堆内存中;
  • 一般不需要、也不推荐程序员手动编写内存回收和垃圾清理代码。

Java GC 主要负责三件事:

  • What:确定哪些内存需要 GC 回收?
  • When:确定什么时候需要 GC 回收?
  • How:如何执行 GC 回收?

二、GC 回收的对象

Java 中那些「不可达的对象」就会变成垃圾,等待被 GC 回收。

"不可达":不能通过任何途径引用该对象。

Java 中定义了四种引用:

  • 强引用:Strong Reference,代码中最普遍的形式,GC 永远不会回收掉这种引用对象;
  • 软引用:Soft Reference,指一些还有用但并非必需的对象,在系统将要发生内存溢出之前,会对这类对象进行回收;
  • 弱引用:Weak Reference,指非必需对象,比软引用的强度更弱一些。当 GC 工作时,无论当前内存是否足够都会回收掉只被弱引用关联的对象;
  • 虚引用:是最弱的一种引用关系,一个对象是否有虚引用的存在完全不影响其生存空间。

在 JVM 的内存空间中,程序计数器、虚拟机栈和本地方法栈三个区域都是线程私有的,随线程一起生或死,在方法结束或线程结束时,占用的内存空间自然就回收了,不需要过多考虑回收的问题。

Java 堆内存和方法区: 这两个区域是线程共享的,内存的分配和回收都是动态的,没有确定性,所以 GC 回收重点关注的就是这两部分内存。

三、GC 回收过程

当前商业虚拟机的垃圾收集基本都采用「分代收集 - Generational Collection」算法,这种算法根据对象的存活周期不同将内存划分为不同的区域,然后根据各个区域的特点采用最适当的收集算法中,从而提高回收效率。

1、图解分代内存

在 HotSpot JVM 中,需要进行 GC 回收的内存空间主要是 Java 堆内存和方法区,所以为了更快、更好的回收内存,对这两个区域进行分代划分。

其中,Java 堆是垃圾收集器管理的重点区域,因此也被称为

GC 堆(Garbage Collected Heap)

,并对堆内存进一步分代划分为 「年轻代和年老代」(也叫新生代和老年代)。

GC 回收内存的分代示意图如下所示:

Java GC垃圾回收机制一、Java GC二、GC 回收的对象三、GC 回收过程

也可以简化成下面这样:

Java GC垃圾回收机制一、Java GC二、GC 回收的对象三、GC 回收过程

我再把它简化一下,并加上中文说明:

Java GC垃圾回收机制一、Java GC二、GC 回收的对象三、GC 回收过程

接下来结合上面的图,分别对三个 Generation 进行文字解析。

1.1 年轻代

  • 堆内存被划分为 年轻代 和 年老代,其中年轻代又分为 Eden、S0、S1,HotSpot JVM 默认的空间比例为:

    Eden:S0:S1 = 8:1:1

  • 几乎所有新生成的对象首先分配在年轻代的 Eden 区,当 Eden 区满了之后,会把还存活的对象依次转移到 S0、S1;
  • 年轻代对象 98% 是朝生夕死的,剩下小部分寿命长的对象会进入年老代;
  • GC 回收年轻代时,效率很高,常规应用进行一次垃圾收集一般可以回收 70%~95% 的空间。

1.2 年老代

  • 年老代对象用于存储长期存活的对象和一些大对象(如大数组、大字符串,需要大量连续存储空间象);
  • 当年轻代空间满了,或者在年轻代中经历了 N 次垃圾回收后仍然存活的对象,都会被放到年老代中;
  • 堆内存可通过

    -Xms -Xmx

    参数调整大小,年轻代可通过

    -Xmn

    调整大小,

    年老代的内存大小 = 堆内存 - 年轻代

1.3 永久代

在 HotSpot JVM 中,习惯把方法区称为「永久代」,本质上两者并不等价,只是因为在 GC 分代收集中使用永久代来实现方法区而已。

  • 方法区主要存储类信息、常量、静态变量等数据;
  • 该区域可通过

    -XX:PermSize -XX:MaxPermSize

    调整内存大小;
  • 该区域的回收目标主要是废弃常量和无用的类,但是回收效率和性价比都很低,GC 很少在该区域执行;
  • JDK 1.8 对内存结构进行了优化,将方法区从永久代抽取出来,去掉了永久代空间,取而代之的是元空间(MetaSpace,Native Memory),因此也不会再出现

    java.lang.OutOfMemoryError: PermGen error

    错误。

2、图解 GC 回收过程

在上面分代内存的示意图基础上,再结合 GC 回收内存的过程,画出 GC 回收过程的简图:

Java GC垃圾回收机制一、Java GC二、GC 回收的对象三、GC 回收过程

对 GC 回收过程具体说明如下:

  • 几乎所有新生成的对象首先存放在年轻代的 Eden 区,大部分对象朝生夕死;
  • 当新对象生成时,在 Eden 申请空间失败(因为空间不足等),会触发一次 GC (Minor GC),先将 Eden 中存活对象复制到 Survivor0 区(简称 S0),然后清空 Eden;
  • 当 S0 也存放满了时,触发 Minor GC ,将 Eden、S0 存活对象复制到另一个 Survivor1 区(简称 S1),然后清空 Eden、S0,最后交换 S0 和 S1,即保持 S1 为空, 如此往复;
  • 当 S1 不足以存放 Eden 和 S0 的存活对象时,会将存活对象直接存放到老年代;
  • 同时,当对象在 Survivor 区躲过一次 GC 的话,会将其对象年龄加 1,默认情况下对象年龄达到 15 岁时,也会移动到老年代中;
  • 当老年代也满了,就会触发一次 Full GC,也就是新生代、老年代都进行回收。

对象分配策略

  • 大部分对象首先分配在年轻代的 Eden 区;
  • 在年轻代中长期存活的对象会在 Minor GC 过程中进入老年代;
  • 大对象(如大数组、大字符串)直接分配在老年代;

3、Minor GC 和 Full GC

Minor GC:

  • 也称年轻代 GC、新生代 GC,指发生在年轻代的垃圾收集动作;
  • 因为 Java 对象大多具备「朝生夕灭」的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。

Full GC:

  • 也称为

    Major GC

    、老年代 GC,指发生在老年代的垃圾收集动作;
  • 执行 Full GC 时,经常会伴随至少一次的 Minor GC;
  • Full GC 的速度一般会比 Minor GC 慢 10 倍以上,所以要尽量避免出现频繁的 Full GC 动作。