之前在项目中做服务端异常测试的时候,一般是用 jstat 监控 gc 变化,对 JVM 的内存回收策略有一些了解,最近趁有时间,整理下之前测试过程中的一些笔记,对其中部分重要的内容再消化一下。

1、JVM 垃圾回收算法

主要有以下几种:
1、标记 - 清除法
首先标记出所有需要回收的对象,使用可达性分析算法判断一个对象是否为可回收,在标记完成后统一回收所有被标记的对象。
说明:1.效率问题,标记和清除两个阶段的效率都不高。2.空间问题,标记清除后会产生大量不连续的内存碎片,以后需要给大对象分配内存时,会提前触发一次垃圾回收动作。

2、复制算法
将内存分为两等块,每次使用其中一块。当这一块内存用完后,就将还存活的对象复制到另外一个块上面,然后再把已经使用过的内存空间一次清理掉。
  说明:1.无内存碎片问题。2.可用内存缩小为原来的一半。 3.当存活的对象数量很多时,复制的效率很慢。

3、标记 - 整理法
  标记过程还是和标记 - 清除算法一样,之后让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
  说明:1.无需考虑内存碎片问题。

4、分代收集法(现在的虚拟机垃圾收集大多采用这种方式)
算法设计思路是:把对象按照寿命长短来分组,分为年轻代、年老代和持久代,不同代上采用最适合的 gc 方式进行回收。
JVM 在每一次执行垃圾收集器时,只是对一小部分内存对象引用进行检查,这一小部分对象的生命周期也更短,从而加快了垃圾收集的性能。
避免了对堆中的所有对象进行检查时所带来的程序的响应的延迟,因为 jvm 执行 GC 时,会终止其它线程的运行,等回收完毕,才恢复其它线程的操作。

《深入分析 Java Web 技术内幕(修订版)》8.6.4 节关于分代垃圾收集算法的讲解很详细:

JVM 将整个堆划分为 Young 区、Old 区和 Perm 区,分别存放不同年龄的对象,这三个区存放的对象有如下区别。
Young 区又分为 Eden 区和两个 Survivor 区,其中所有新创建的对象都在 Eden 区,当 Eden 区满后会触发 young GC 将 Eden 区仍然存活的对象复制到其中一个 Survivor 区中,另外一个 Survivor 区中的存活对象也复制到这个 Survivor 中,以保证始终有一个 Survivor 区是空的。

Old 区存放的是 Young 区的 Survivor 满后触发 young GC 后仍然存活的对象,当 Eden 区满后会将对象存放到 Survivor 区中,如果 Survivor 区仍然存不下这些对象,GC 收集器会将这些对象直接存放到 Old 区。如果在 Survivor 区中的对象足够老,也直接存放到 Old 区。如果 Old 区也满了,将会触发 Full GC,回收整个堆内存。

Perm 区存放的主要是类的 Class 对象,如果一个类被频繁地加载,也可能会导致 Perm 区满,Perm 区的垃圾回收也是由 Full GC 触发的。

Young 区存放所有新生成的对象,Perm 区用于存放静态文件,如 Java 类、方法等。
Old 区都是一些生命周期较长的对象,所以年老代收集频率不像年轻代那么频繁,这样就减少了每次垃圾收集时所要扫描的对象的数量,从而提高垃圾回收效率。
在新生代中,由于对象生存期短,每次回收都会有大量对象死去,那么这时就采用复制算法。老年代里的对象存活率较高,没有额外的空间进行分配担保,所以可以使用标记 - 整理 或者 标记 - 清除。

二、使用 jstat 监控 GC 情况

jstat 命令简介

Jstat 是 JDK 自带的一个轻量级小工具,全称 “Java Virtual Machine statistics monitoring tool”,它位于 java 的 bin 目录下,主要利用 JVM 内建的指令对 Java 应用程序的资源和性能进行实时的命令行的监控,包括了对 Heap size 和垃圾回收状况的监控。

语法结构:

$ jstat -help
Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

option:可选参数很多,详情可自行百度,一般用-gcutil 查看垃圾收集 (gc) 情况
vmid:VM 的进程号,即当前运行的 java 进程号
interval:执行的间隔时间,单位为毫秒
count :打印次数,如果缺省则一直打印

jstat -gcutil 使用

下面是项目测试过程中的截图:
其中,jstat -gcutil 49 1000,表示查看进程号是 49 的进程的 gc 情况,每隔 1000ms 打印一次。

a 处 gc 说明:
1、a 处发生了一次 young gc,扫描 E 区和 S1 区,将仍然存活的对象复制到 S0 区,如果存在扫描很多次仍然存活的对象,将其移至 O 区,因此一次 young gc 后,E 区使用率有所下降,O 区有所上升;
2、YGC 增加 1,YGCT 增加 0.005s(1.713-1.708),为本次 gc 耗时。


↙↙↙阅读原文可查看相关链接,并与作者交流