在JVM应用的运维和问题排查过程中,查看当前进程已加载类的全限定名以及对应的类加载器信息,是分析类冲突、类加载异常等问题的常用手段。通过JDK自带的工具,我们可以仅用一条命令就完成这个需求,不需要额外编写任何Java代码。

核心命令介绍
JDK提供的jcmd工具是执行这条命令的核心,它可以直接向运行的JVM进程发送诊断命令。要打印所有已加载类的全限定名和加载器,使用的命令格式如下:
# 先通过jps获取目标JVM进程的PID jps -l # 执行命令打印类信息,替换<pid>为实际进程ID jcmd <pid> GC.class_histogram -all
如果只需要查看类信息不需要统计实例数量,也可以使用jcmd <pid> VM.classloader_stats命令,但前者输出的信息更完整,包含全限定名和加载器的对应关系。
命令参数解析
- jcmd:JDK自带的命令行诊断工具,位于JDK的bin目录下,需要保证环境变量配置正确。
- <pid>:目标JVM进程的进程ID,可以通过
jps、ps -ef | grep java等命令获取。 - GC.class_histogram:jcmd的子命令,用于生成类的直方图统计,加上
-all参数后会输出所有已加载的类,包括未实例化的类。
输出结果说明
执行上述命令后,输出结果会包含多列信息,其中和类全限定名、加载器相关的核心列如下:
| 列名 | 含义 |
|---|---|
| class name | 类的全限定名,例如java.lang.String、com.example.UserService |
| class loader | 加载该类的类加载器信息,会显示加载器的类型和标识 |
| instances | 该类当前的实例数量 |
| bytes | 该类所有实例占用的内存大小,单位为字节 |
其中class loader列的内容会明确标识类是由哪个加载器加载的,比如bootstrap class loader表示启动类加载器,app class loader表示应用程序类加载器,自定义类加载器也会显示对应的标识信息。
代码示例验证
我们可以编写一个简单的Java程序,自定义一个类加载器加载指定类,然后通过命令验证输出结果:
import java.io.FileInputStream;
import java.lang.reflect.Method;
// 自定义类加载器
class CustomClassLoader extends ClassLoader {
private String classPath;
public CustomClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
String filePath = classPath + name.replace('.', '/') + ".class";
FileInputStream fis = new FileInputStream(filePath);
byte[] classData = new byte[fis.available()];
fis.read(classData);
fis.close();
return defineClass(name, classData, 0, classData.length);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
}
}
public class ClassLoadTest {
public static void main(String[] args) throws Exception {
// 等待一会方便执行jcmd命令
Thread.sleep(300000);
}
}
编译运行上述程序后,通过jcmd执行对应命令,就能在输出结果中看到自定义类加载器加载的类信息,验证命令的有效性。
注意事项
- 执行命令的用户需要和启动JVM进程的用户一致,否则可能没有权限访问目标进程。
- 如果JVM进程开启了安全管理器,可能需要对应的权限才能执行该诊断命令。
- 输出结果可能比较长,可以通过管道符配合
grep过滤指定包名的类,例如jcmd <pid> GC.class_histogram -all | grep com.example。 - 该命令会触发JVM的全局安全点,对线上运行的高并发应用可能会有短暂的停顿,建议在低峰期执行。