文章目录
- 一、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 回收内存的分代示意图如下所示:
也可以简化成下面这样:
我再把它简化一下,并加上中文说明:
接下来结合上面的图,分别对三个 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 回收过程的简图:
对 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:
- 也称为
、老年代 GC,指发生在老年代的垃圾收集动作;Major GC
- 执行 Full GC 时,经常会伴随至少一次的 Minor GC;
- Full GC 的速度一般会比 Minor GC 慢 10 倍以上,所以要尽量避免出现频繁的 Full GC 动作。