Java虚拟机的垃圾回收机制

没有最好的GC收集器,只有最适合的GC收集器。

垃圾收集

在Java语言中,程序员通过关键字new来创建对象,当使用完创建出来的对象后,程序员无需手动释放内存。这部分释放内存的工作就叫做垃圾收集,由JVM来承担。

哪些内存需要回收

JVM将内存划分为几个区域:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区(JDK8中已被废弃,采用名为MetaSpace的区域来替代,这部分内存使用native memory即本地内存)。以上的==程序计数器、Java虚拟机栈、本地方法栈不用考虑回收问题==。因为都是线程私有的区域,随着线程的产生而产生、线程的销毁而销毁,并且这部分空间大小的分配、回收,在编译期间就可以确定,当方法结束或者线程结束时进行回收。
Java堆和方法区(==JDK8改为MetaSpace,这部分待查阅资料后补充==),由于一个类的不同实现类需要的内存不一样,一个方法的多个分支创建的对象也不同,这一部分内存在编译期是不确定的,只有真正运行程序时才能明确。==Java堆和方法区,这部分内存的分配和回收都是动态的,垃圾回收机制也是在这一个区域内讨论==

如何确定对象需要回收

在垃圾收集之前,必须先知道哪些对象不再被任何其他途径使用了。垃圾收集器进行处理的也是这一类对象。

引用计数算法(Python采用)

为对象保存一个引用计数器,当对象被一个地方引用时,就将这个计数器加一,有一个地方取消引用时,就将计数器减一。任何时刻计数器为0,就说明这个对象不可能再被使用

可达性分析(HotSpot JVM采用)

可达性分析(Reachability Analysis)通过一系列的”GC Root”的对象作为起始点,从这些节点向下搜索,经过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连的话,这个对象不可达。判定为可以被回收的对象

Java中的GC Roots

  • 虚拟机栈(栈侦中的本地变量表)引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象

四种引用类型

强引用、软引用、弱引用、虚引用

回收过程

==为什么要进行两次标记??==

方法区回收

方法区回收的是:废弃的变量、无用的类

废弃的变量
无用的类
  • 类的所有实例都已经被回收,Java堆中没有该类的实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class没有在任何地方被引用,无法再任何地方通过反射访问该类方法
    相关VM Options:-verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnLoading

垃圾收集算法

几种收集算法的思想、优缺点和发展过程

标记-清除算法

分为标记、清除两个阶段。

缺点

  • 标记和清除两个过程都需要对对象进行遍历,效率不高
  • 标记清除后会产生大量的不连续的内存碎片

复制算法

将内存按容量分为两块同样大小的区域,每次只使用其中一块。当一块内存用完就将存活的对象复制到另一块区域中,之后清除上一块区域的空间。

优点

  • 相对于标记-清除算法,复制算法效率高,而且不会产生内存碎片

缺点

  • 浪费了一半内存空间

标记-整理算法(Mark-Compact)

复制算法在对象存活率高的情况,需要较多的复制操作,效率变低。
标记-整理的标记过程和标记-清除算法的标记过程一致,只是在标记之后,并不是进行清除操作,而是让所有存活对象都向一端移动。最后直接清除边界以外未被存活对象占用的空间。

优点

  • 不会产生内存碎片
  • 不浪费内存空间

缺点

  • 整理需要计算量,拉低了效率

分代收集算法

复制算法和标记-整理算法,都具有不同的优点和缺点,并且适用于不同的场景。现代虚拟机采用分代收集算法,在不同存活周期的对象区域应用这两种收集算法(复制、标记-整理)。

  • 由于新生代对象的存活率较低,因此适用于使用复制算法。而且新生代内存区域分为一个Eden区、两个Survivor区。每次只使用一个Eden区、一个Survivor区(这两个区域的内存与新生代内存区域总量占比默认为8:1)

  • 老年代对象存活率较高,因此采用标记-整理算法减少复制过多对象的情况

如何发起内存回收

如何进行内存回收动作,即垃圾收集器如何工作

Serial收集器

单线程,使用复制算法,用于回收新生代的收集器。 触发GC时,Stop The World
使用场景:

  • Client模式下的JVM

ParNew收集器

多线程,使用复制算法,用于回收新生代的收集器。其实就是Serial收集器的多线程版本。默认启用与服务器相同核心的线程数;触发GC时,Stop The World
使用场景:

  • 多核CPU的服务器下,可以充分利用CPU资源提高回收效率

Parallel Scavenge收集器(吞吐量为目标)

多线程,使用复制算法,用于回收新生代的收集器,以吞吐量为目标。(吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
关键参数:

  • -XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间
  • -XX:MaxTimeRatio 设置吞吐量大小
  • -XX:+UseAdaptiveSizePolicy 虚拟机根据运行状态自动调节新生代最大内存、Eden与Survivor的比例、晋升老年代对象的大小阀值

Serial Old收集器

Serial的老年代版本,使用标记-整理算法,用于回收老年代的收集器。与Parallel Scavenge搭配使用;==作为CMS收集器发生Concurrent Model Failure时的后备预案==

Parallel Old收集器

Parllel Scavenge的老年代版本,使用标记-整理算法。在JDK1.6中提供,==与Parallel Scavenge搭配使用,在注重吞吐量以及CPU资源敏感的场合优先考虑==

CMS收集器(Concurrent Mark Sweep)

基于标记-清除算法,以获取最短回收停顿时间为目标的收集器。适用于互联网网站、B/S系统的服务端场景

缺点:

  • 对CPU资源敏感,由于并发标记、并发清除这两个阶段会启动线程与用户线程并发运行,因此对服务器会有CPU资源消耗。
  • 由于并发标记、并发清除是与用户线程并发运行的,因此需要预留一定的空间,避免在进行回收阶段时,没有足够的空间供用户线程使用。这将触发Concurrent Model Failure,将使用Serial Old来完成回收操作,导致回收时间过长
  • 由于CMS基于标记-清除算法,因此会产生大量内存碎片。会造成老年代区有很大空间剩余,但是无法分配大对象,这时需要触发一次Full GC。通过参数-XX:+UseCMSCompactAtFullCollection开关参数(默认开启),用于进行Full GC时开启内存碎片的合并整理过程(内存整理的过程是无法并发的,==Stop The World==),-XX:CMSFullGCsBeforeCompaction,用于设置多少次不压缩Full GC后,进行一次带压缩的Full GC(默认为0,即每次Full GC都进行压缩)

G1收集器(Garbage-First Garbage Collector)

参考美团技术团队

垃圾收集器总结

参考 www.fasterj.com
在JVM中的进程名 | 用于收集哪个内存区域 | 启用命令行参数 | 描述 | 组合搭档
—|—|—|—|—
Copy | 新生代 | -XX:+UseSerialGC | 使用复制算法,单线程收集 | MarkSweepCompact
PS Scavenge | 新生代 | -XX:+UseParallelGC | 使用复制算法,多线程并发收集 | PS MarkSweep
ParNew | 新生代 | -XX:+UseParNewGC | 使用复制算法,多线程并发收集 | MarkSweepCompact
G1 Young Generation | 新生代 | -XX:+UseG1GC | 整体是标记-整理算法,region之间使用复制算法 | G1 Mixed Generation
MarkSweepCompact | 老年代 | -XX:+UseSerialGC | 使用标记-清除算法,可选的整理算法,单线程收集
PS MarkSweep | 老年代 | -XX:+UseParallelOldGC | 多线程标记-整理算法
ConcurrentMarkSweep | 老年代 | -XX:+UseConcMarkSweepGC | 标记-清除算法并发收集,较短的Stop-The-World时间,如果发生Concurrent-Mode-Failure时,使用MarkSweepCompact收集器作为预案
G1 Mixed Generation | 老年代 | -XX:+UseG1GC | 使用’Garbage First’ 算法

如何查看Java进程使用什么垃圾收集器?

1
2
3
4
5
6
// 通过MXBean来获取垃圾收集器的信息
List<GarbageCollectorMXBean> beans = ManagementFactory.getGarbageCollectorMXBeans();

for (GarbageCollectorMXBean bean : beans) {
System.out.println(bean.getName());
}

环境

1
java -XX:+PrintCommandLineFlags -version

输出结果为

1
2
PS Scavenge
PS MarkSweep

GC日志分析工具

GC日志分析工具

参考资料

  • 《深入理解Java虚拟机》