概述

在这个教程中,我们将学习在Java虚拟机(JVM)中列出所有加载的类的不同方法。例如,我们可以加载JVM的堆转储或者将运行的应用程序连接到各种工具,并在该工具中列出加载的所有类。此外,还有多种库可以实现这一点。

我们将探索非编程和编程两种方法。

非编程方法

1. 使用VM参数

列出所有已加载类的最直接方法是在控制台输出或文件中记录。

我们将使用以下JVM参数运行Java应用程序:

java <app_name> --verbose:class
[Opened /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar]
[Loaded java.lang.Object from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.io.Serializable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.Comparable from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.CharSequence from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.String from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.reflect.AnnotatedElement from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.reflect.GenericDeclaration from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.reflect.Type from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
[Loaded java.lang.Class from /Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/rt.jar] 
...............................

对于Java 9,我们将使用-Xlog JVM参数将加载到文件中的类信息日志化:

java <app_name> -Xlog:class+load=info:classloaded.txt

2.2. 使用堆转储

我们将了解不同的工具如何使用JVM的堆转储来提取类加载信息。首先,我们使用以下命令生成堆转储:

jmap -dump:format=b,file=/opt/tmp/heapdump.bin <app_pid>

这个堆转储文件可以在各种工具中打开,以获取不同的指标。

在Eclipse中,我们将在Eclipse Memory Analyzer中加载名为heapdump.bin的堆转储文件,并使用直方图界面:

Eclipse直方图

然后,我们将在Java的VisualVM界面中打开名为heapdump.bin的堆转储文件,并使用按实例或大小选项查看类:

VisualVM堆转储

2.3. JProfiler

JProfiler 是一个顶级的Java应用性能分析器,拥有丰富的功能来查看不同的指标。

JProfiler中,我们可以附加到正在运行的JVM或加载堆转储文件,并获取所有与JVM相关的指标,包括加载的所有类名。

我们将使用附加进程功能让JProfiler连接到运行的应用程序ListLoadedClass

JProfiler附加进程

然后,我们将捕获应用程序的快照,并使用它来获取所有加载的类:

JProfiler快照

下面,我们可以看到使用堆步行功能显示加载类的实例计数:

JProfiler堆步行

编程方法

3.1. Instrumentation API

Java提供了Instrumentation API,帮助我们获取应用程序的有价值指标。首先,我们需要创建并加载一个Java代理,以便将Instrumentation接口的实例引入到应用程序中。Java代理是一种工具,用于对运行在JVM上的程序进行调试。

然后,我们需要调用Instrumentation方法[getInitiatedClasses(ClassLoader loader)](https://docs.oracle.com/en/java/javase/21/docs/api/java.instrument/java/lang/instrument/Instrumentation.html#getInitiatedClasses\(java.lang.ClassLoader\)),以获取由特定类加载器类型加载的所有类。

3.2. Google Guava

我们将了解Guava库如何使用当前类加载器获取JVM中加载的所有类的列表。

首先,让我们在Maven项目中添加Guava依赖项

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

然后,我们将初始化ClassPath对象,使用当前类加载器实例:

ClassPath classPath = ClassPath.from(ListLoadedClass.class.getClassLoader());
Set<ClassInfo> classes = classPath.getAllClasses();
Assertions.assertTrue(4 < classes.size());

3.3. 反射API

我们将使用Reflections库,它会扫描当前类路径并在运行时查询它。

首先,我们在Maven项目中添加reflections依赖:

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.10.2</version>
</dependency>

现在,让我们看看示例代码,它返回包下的类集合:

Reflections reflections = new Reflections(packageName, new SubTypesScanner(false));
Set<Class> classes = reflections.getSubTypesOf(Object.class)
  .stream()
  .collect(Collectors.toSet());
Assertions.assertEquals(4, classes.size());

结论

在这篇文章中,我们学习了在JVM中列出所有加载类的各种方式。首先,我们了解了如何使用VM参数来记录已加载类的列表。

接着,我们探讨了各种工具如何加载堆转储或连接到JVM,以显示包括已加载类在内的各种指标。最后,我们涵盖了部分Java库。

如往常一样,所有的代码都可在GitHub上找到。