AppClassLoader的classpath路径到底是什么路径呢

20220103180338

我们总能听到同事们讨论class loader时说AppClassLoader负责加载classpath下的类。然后我想问:AppClassLoader的classpath路径到底是什么路径呢?又加载这个路径下的哪些class文件呢?
如果你也有这个疑问,那一起继续往下看

AppClassLoader的classpath路径到底是什么路径呢?

三个典型的class loader

java预设了三个class loader,在形式上构成了所谓的双亲委派体系。在关系上除了Bootstrap ClassLoader,其余都继承自ClassLoader.java类。java中不同的class loader加载不同的目录(这里的目录我们简单起见专指本地磁盘再限定下,我们这里专指目录下的jar包的加载)。我们通过这三个class loader,来找到我们的答案

BootstrapClassLoader

class 它是C语言实现的

负责 JDK 核心类库的加载,如:rt.jar。一般为存放在 <JAVA_HOME>\jre\lib 目录下的,或者是被 Xbootstrappath 参数所指定的路径中的.class文件。可以使用System.getProperty("sun.boot.class.path")来显示路径。预设是JRE所在目录的classes下之.class档案,或lib目录下.jar档案中.class文件。下图所示默认加载的路径
20220110173045

Extension ClassLoader

class sun.misc.Launcher$ExtClassLoader

负责 JDK 扩展类库的加载。一般为<JAVA_HOME>\lib\ext 目录中,或者被 java.ext.dirs 系统变量所指定的路径中的所有class类。预设是JRE目录下的lib\ext\classes目录下的.class档案,或lib\ext目录下的.jar档案中的类别并载入。以使用System.getProperty(“java.ext.dirs”)来显示路径。下图所示默认加载的路径

1
2
3
4
5
6
System.getProperty("java.ext.dirs"):
/Users/yaoliang/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions:/System/Library/Java/Extensions
/usr/lib/java

20220103175542

SystemClassLoader(AppClassLoader)

class sun.misc.Launcher$AppClassLoader

负责 程序自己classpath路径下的类 的加载,程序一般是直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载,一般情况下这个就是程序中默认的类加载器。预设是目前classpath路径下的.class文件。可以使用System.getProperty(“java.class.path”)来显示路径,即classpath所指定的路径。这里的“程序自己classpath路径下的类”这就是我们的第一个重点。classpath路径到底是什么路径呢?

我们举例来说

我们日常使用的是springboot技术栈,它打的的jar包是经过自身处理的,已经不是java原生的结构了,它是spring自定义的class loader加载的,我们暂叫它app-xx.jar。然后我们再通过agent打个jar,agent打的jar包是java原生的结构,是通过SystemClassLoader加载的,暂叫它agent-xx.jar。所以agent-xx.jar更能说明 “classpath路径到底是什么路径呢?” 这个问题

现在我们通过JD-GUI反编译工具看下两个jar的内部
20220103175625

为了看到加载过程,我们需要运行起来。注意:这里一定要使用java -jar的方式运行,通过idea运行是不可以的。为了观察运行过程,我们采用远程debug的方式。命令执行java -javaagent:/xxx/agent-xx.jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5009 -jar /xxx/app.jar

补充一点:每个class loader都有一个容器,这个容器是path的集合,这个path可以理解为一个base目录(实践中base目录即为agent-xx.jar包的真实物理路径),所以每个class loader都包含一个base目录集合。当加载一个class的时候,通过拼接 base目录+class全限定名的目录形成一个完成的目录,然后加载就成为去找目录形成file文件,再到形成stream流的过程。

引申1

从agent-xx.jar的内部结构,我们知道,这就是类的全限定名组成的啊。所以当程序加载某个class时,传入全限制类名作为参数,然后sun.misc.Launcher$AppClassLoader将全限制类名拼接上自身的base目录。这就是sun.misc.Launcher$AppClassLoader加载的过程。所以SystemClassLoader的classpath路径到底是什么路径呢?答案就是agent-xx.jar,最关键的是加载的是这个jar包的第一层的全限定类名的这些.class文件。即假如agent-xx.jar有个lib文件夹,里面是全限定类名的.class文件,SystemClassLoader也不会加载他们

20220103175746

现在,我们给出了classpath路径到底是什么路径呢?的答案。再看图3的app-xx.jar的内部结构,也明白spring boot自定义的可运行jar包为什么是这个结构了吧。第一层只有springboot的loader的.class全限定名文件,sun.misc.Launcher$AppClassLoader 加载loader的.class全限定名文件,这其中就包含springboot的自定义的class loader:LaunchedURlClassLoader,它开始按照自定义的逻辑加载app-xx.jar中的BOOT-INF里的classes和lib包下的jar, 所以引出另一个技术点:springboot的程序真正的入口是org.springframework.boot.loader.JarLauncher.main方法了,这个知识点自己探索吧。

提示:项目里引入

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-loader</artifactId>
<version>2.4.4</version>
</dependency>

引申2

在理解了AppClassLoader加载的classpath路径到底是什么,以及加载这个路径下的哪些.class后,然后在探索和使用agent技术时,遇到agent常见的

ClassNotFoundException问题,NoClassDefFoundError 问题时,更快的认清和解决问题。

参考:

  1. https://openhome.cc/Gossip/JavaEssence/ClassLoader.html
  2. https://www.zhihu.com/question/49667892
  3. https://ken-ljq.github.io/2018/JVM%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7Agent%E8%AE%BE%E8%AE%A1%E5%AE%9E%E7%8E%B0-%E4%BA%8C/