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>
例如,我们的应用程序现在使用G1 GC运行:
>> 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的方法,这是始终需要的。
Post Directory
