JVM垃圾回收机制

JVM具备自动内存管理机制,这个机制虽然可以减轻很多工作量,但是完全交由JVM处理,也会增加回收性能的不确定性。尤其是在一些特殊的业务场景下,不合适的垃圾回收算法以及策略,都有可能导致系统性能下降。因此,我们有必要了解一下JVM的垃圾回收机制。

一、GC机制

在认识GC算法之前,我们需要先弄清楚3个问题。第一,回收发生在哪里?第二,对象在什么时候可以被回收?第三,如何回收这些对象?

1.1、回收发生在哪里? - Where

JVM垃圾回收发生在哪一块内存中?

在JVM的内存模型中,程序计数器、虚拟机栈和本地方法栈这3个区域是线程私有的,随着线程的创建而创建,销毁而销毁。因此,这三个区域的内存分配和回收都具有确定性。

垃圾回收的重点是堆和方法区中的数据,堆中被回收的主要是对象,方法区中被回收的主要是废弃常量和无用的类。

1.2、对象在什么时候可以被回收? - When

JVM如何判断一个对象是可以被回收的呢?

一般一个对象不再被引用,就代表该对象可以被回收。目前有以下两种算法可以判断一个对象是否可以被回收。

  • 引用计数算法,这种算法是通过每个对象的引用计算器来判断对象是否被引用。每当对象被引用,引用计数器就会加1;每当引用失效,计数器就会减1。当对象的引用计数器的值为0时,就说明该对象不再被引用,可以被回收了。这里需要注意的是,引用计数算法虽然实现简单、判断效率也很高,但是存在着对象之间互相循环引用的问题。
  • 可达性分析算法,该算法的基础是GC Roots。GC Roots是所有对象的根对象,在JVM加载时,会创建一些普通对象作为正常对象的引用。这些普通对象作为正常对象的起始点,在垃圾回收时,会从GC Roots开始向下搜索,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。目前,HotSpot虚拟机采用的就是这种算法。

以上两种算法都是通过引用来判断对象是否可以被回收的。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为了以下四种:

  • 强引用(Strong Reference),被强引用关联的对象,永远不会被回收
  • 弱引用(Weak Reference),只被弱引用关联的对象,只要发生垃圾回收事件就会被回收
  • 软引用(Soft Reference),被软引用关联的对象,只用当系统将要发生内存溢出时才会被回收
  • 虚引用(Phantom Reference),被虚引用关联的对象,其唯一的作用是在对象被回收时能收到一个系统通知

1.3、如何回收这些对象? - How

垃圾回收线程是如何回收对象的?

JVM垃圾回收遵循以下两个特性:

  • 自动性,Java提供了一个系统级的线程来跟踪每一块被分配出去的内存空间,当JVM处于空闲循环时,垃圾回收线程就会自动检查每一块被分配出去的内存空间,然后自动回收空闲的内存块。
  • 不可预期性,即使一个对象没有被引用,JVM也不一定会回收它。我们很难确定一个没有被引用的对象,是不是会被立刻回收掉,因为有可能当程序结束后,这个对象仍在内存中。

垃圾回收线程在JVM中是自动执行的,Java程序无法强制执行。我们唯一能做的就是通过调用System.gc方法来“建议”执行垃圾回收,但是否可执行,什么时候执行?仍然是不可预期的。

二、GC算法

JVM提供了几种不同的回收算法来实现其回收机制。垃圾收集器实现的回收算法可以分为以下几种:

回收算法类型 优点 缺点
标记 - 清除算法(Mark - Sweep) 不需要移动对象,简单高效 标记 - 清除过程效率低,容易产生内存碎片
复制算法(Copying) 不会产生内存碎片,简单高效 内存使用率低,且有可能产生频繁复制问题
标记 - 整理算法(Mark - Compact) 综合了前两种算法的优点 仍需要移动局部对象
分代收集算法(Generational Collection) 分区回收 对于有长时间存活对象的场景,回收效果不明显,甚至起到反作用

三、垃圾回收器

如果说收集算法是内存回收的方法论,那么垃圾回收器就是内存回收的具体实现,JDK1.7 update14之后HotSpot虚拟机支持的所有回收器如下(注:以下均为服务端回收器):

回收器类型 回收算法 特点 设置参数
Serial New / Serial Old回收器 复制算法 / 标记 - 整理算法 单线程复制回收,简单高效,但会暂停程序导致停顿 -XX:+UseSerialGC(年轻代、老年代回收器分别为:Serial New、Serial Old)
ParNew New / ParNew Old回收器 复制算法 / 标记 - 整理算法 多线程复制回收,降低了停顿时间,但容易增加上下文切换 -XX:+UseParNewGC(年轻代、老年代回收器分别为:ParNew New、Serial Old,JDK1.8中无效)                               -XX:+UseParallelOldGC(年轻代、老年代回收器分别为:Parallel Scavenge、Parallel Old)
Parallel Scavenge回收器 复制算法 并行回收器,追求高吞吐量,高效利用CPU -XX:+UseParallelGC(年轻代、老年代回收器分别为:Parallel Scavenge、Serial Old)                                                           -XX:ParallelGCThreads=4(设置并发线程)
CMS回收器 标记 - 清除算法 老年代回收器,高并发、低停顿,追求最短GC回收停顿时间,CPU占用比较高,响应时间快,停顿时间短 -XX:+UseConcMarkSweepGC(年轻代、老年代回收器分别为:ParNew New、CMS(Serial Old作为备用))
G1回收器 标记 - 整理 & 复制算法 高并发、低停顿,可预测停顿时间 -XX:+UseG1GC(年轻代、老年代回收器分别为:G1、G1)                                   -XX:MaxGCPauseMillis=200(设置最大暂停时间)

注:在JVM规范中并没有明确GC的运作方式,各个厂商可以采用不同的方式实际垃圾回收器。

我们可以通过JVM工具查询当前JVM使用的垃圾回收器类型,首先通过ps命令查询出进程ID,再通过jmap -heap 进程ID查询出JVM的配置信息,其中就包括垃圾回收器的设置类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# jmap -heap 11200
Attaching to process ID 11200, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.281-b09

using thread-local object allocation.
Garbage-First (G1) GC with 13 thread(s)

Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 1363144 (1.2999954223632812MB)
MaxNewSize = 643825664 (614.0MB)
OldSize = 5452592 (5.1999969482421875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 260046848 (248.0MB)
MaxMetaspaceSize = 268435456 (256.0MB)
G1HeapRegionSize = 1048576 (1.0MB)

Heap Usage:
G1 Heap:
regions = 1024
capacity = 1073741824 (1024.0MB)
used = 16329768 (15.573280334472656MB)
free = 1057412056 (1008.4267196655273MB)
1.5208281576633453% used
G1 Young Generation:
Eden Space:
regions = 9
capacity = 49283072 (47.0MB)
used = 9437184 (9.0MB)
free = 39845888 (38.0MB)
19.148936170212767% used
Survivor Space:
regions = 7
capacity = 7340032 (7.0MB)
used = 7340032 (7.0MB)
free = 0 (0.0MB)
100.0% used
G1 Old Generation:
regions = 0
capacity = 1017118720 (970.0MB)
used = 0 (0.0MB)
free = 1017118720 (970.0MB)
0.0% used


6273 interned Strings occupying 451720 bytes.

四、小结

在这篇文章中,我们主要提到了如下概念:

  • GC一般发生在堆和方法区中。当一个对象没有被引用时,该对象就可以被回收。而在回收时,JVM会遵循自动性和不可预测性的原则。
  • 常用的GC算法有四种,分别是标记-清除算法、复制算法、标记-整理算法和分代收集算法。
  • 垃圾回收器是GC算法的具体实现。HotSpot虚拟机支持的回收器有Seria回收器、ParNew回收器、Parallel回收器、CMS回收器和G1回收器。

JVM垃圾回收机制
https://kuberxy.github.io/2020/09/26/JVM垃圾回收机制/
作者
Mr.x
发布于
2020年9月26日
许可协议