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 |
|
四、小结
在这篇文章中,我们主要提到了如下概念:
- GC一般发生在堆和方法区中。当一个对象没有被引用时,该对象就可以被回收。而在回收时,JVM会遵循自动性和不可预测性的原则。
- 常用的GC算法有四种,分别是标记-清除算法、复制算法、标记-整理算法和分代收集算法。
- 垃圾回收器是GC算法的具体实现。HotSpot虚拟机支持的回收器有Seria回收器、ParNew回收器、Parallel回收器、CMS回收器和G1回收器。