1. 概述
除了编译器和运行时等典型的开发实用程序之外,每个 JDK 版本还附带大量其他工具。其中一些工具可以帮助我们获得对正在运行的应用程序的宝贵见解。
在本文中,我们将了解如何使用此类工具来了解有关特定 JVM 实例使用的GC 算法的更多信息。
2. 示例应用
在整篇文章中,我们将使用一个非常简单的应用程序:
public class App {
public static void main(String[] args) throws IOException {
System.out.println("Waiting for stdin");
int read = System.in.read();
System.out.println("I'm done: " + read);
}
}
显然,这个应用程序会等待并继续运行,直到它从标准输入接收到一些内容。这种暂停有助于我们模仿长时间运行的 JVM 应用程序的行为。
为了使用这个应用程序,我们必须使用 javac 编译 App.java 文件,然后使用 java 工具运行它。
3.查找JVM进程
要查找 JVM 进程使用的 GC,首先,我们应该识别该特定 JVM 实例的进程 ID。 假设我们使用以下命令运行我们的应用程序:
>> java App
Waiting for stdin
如果我们安装了JDK,查找JVM实例的进程ID的最佳方法是使用 jps 工具。例如:
>> jps -l
69569
48347 App
48351 jdk.jcmd/sun.tools.jps.Jps
如上所示,系统上运行着三个JVM实例。显然,第二个 JVM 实例(“App”)的描述与我们的应用程序名称相匹配。因此,我们要查找的进程 ID 是 48347。
除了 jps 之外,我们始终可以使用其他通用实用程序来过滤正在运行的进程。例如, procps包中著名的 ps 工具也可以使用:
>> ps -ef | grep java
502 48347 36213 0 1:28AM ttys037 0:00.28 java App
然而, jps 使用起来更简单,并且需要更少的过滤。
4. 二手GC
现在我们知道了如何查找进程 ID,接下来我们来查找已运行的 JVM 应用程序使用的 GC 算法。
4.1. Java 8 及更早版本
如果我们使用 Java 8,我们可以使用 jmap 实用程序来打印堆摘要、堆直方图,甚至生成堆转储 。为了找到 GC 算法,我们可以使用 -heap 选项:
>> jmap -heap <pid>
因此,在我们的特定情况下,我们使用 CMS GC:
>> jmap -heap 48347 | grep GC
Concurrent Mark-Sweep GC
对于其他 GC 算法,输出几乎相同:
>> jmap -heap 48347 | grep GC
Parallel GC with 8 thread(s)
4.2. Java 9+: jhsdb jmap
从 Java 9 开始,我们可以使用 jhsdb jmap 组合来打印有关 JVM 堆的一些信息。 更具体地说,这个特定命令与前一个命令等效:
>> jhsdb jmap --heap --pid <pid>
例如,我们的应用程序现在正在使用 G1GC 运行:
>> jhsdb jmap --heap --pid 48347 | grep GC
Garbage-First (G1) GC with 8 thread(s)
4.3. Java 9+: jcmd
在现代 JVM 中, jcmd 命令非常通用。例如, 我们可以使用它来获取有关堆的一些常规信息 :
>> jcmd <pid> VM.info
因此,如果我们传递应用程序的进程 ID,我们可以看到该 JVM 实例正在使用 Serial GC:
>> jcmd 48347 VM.info | grep gc
# Java VM: OpenJDK 64-Bit Server VM (15+36-1562, mixed mode, sharing, tiered, compressed oops, serial gc, bsd-amd64)
// omitted
G1 或 ZGC 的输出类似:
// ZGC
# Java VM: OpenJDK 64-Bit Server VM (15+36-1562, mixed mode, sharing, tiered, z gc, bsd-amd64)
// G1GC
# Java VM: OpenJDK 64-Bit Server VM (15+36-1562, mixed mode, sharing, tiered, compressed oops, g1 gc, bsd-amd64)
借助一点 grep 魔法,我们还可以消除所有这些噪音并只获取 GC 名称:
>> jcmd 48347 VM.info | grep -ohE "[^\s^,]+\sgc"
g1 gc
4.4.命令行参数
有时,我们(或其他人)在启动 JVM 应用程序时显式指定 GC 算法。例如,我们选择在这里使用 ZGC:
>> java -XX:+UseZGC App
在这种情况下,有更简单的方法来查找已使用的 GC。基本上, 我们所要做的就是以某种方式找到应用程序执行的命令 。
例如,在基于UNIX的平台上,我们可以再次使用 ps 命令:
>> ps -p 48347 -o command=
java -XX:+UseZGC App
从上面的输出可以明显看出,JVM正在使用ZGC。同样, jcmd 命令也可以打印命令行参数 :
>> jcmd 48347 VM.flags
84020:
-XX:CICompilerCount=4 -XX:-UseCompressedOops -XX:-UseNUMA -XX:-UseNUMAInterleaving -XX:+UseZGC // omitted
令人惊讶的是,如上所示,此命令将打印隐式和显式参数以及可调参数 。因此,即使我们没有明确指定 GC 算法,它也会显示所选的默认算法:
>> jcmd 48347 VM.flags | grep -ohE '\S*GC\s'
-XX:+UseG1GC
更令人惊讶的是,这也适用于 Java 8:
>> jcmd 48347 VM.flags | grep -ohE '\S*GC\s'
-XX:+UseParallelGC
5. 结论
在本文中,我们看到了查找特定 JVM 实例使用的 GC 算法的不同方法。提到的一些方法与特定的 Java 版本相关,有些是可移植的。
此外,我们还看到了几种查找进程 ID 的方法,这是始终需要的。