我们总能听到同事们讨论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文件。下图所示默认加载的路径
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 | System.getProperty("java.ext.dirs"): |
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的内部
为了看到加载过程,我们需要运行起来。注意:这里一定要使用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也不会加载他们
现在,我们给出了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 | <dependency> |
引申2
在理解了AppClassLoader
加载的classpath路径到底是什么,以及加载这个路径下的哪些.class后,然后在探索和使用agent技术时,遇到agent常见的
ClassNotFoundException
问题,NoClassDefFoundError
问题时,更快的认清和解决问题。
参考: