关于classloader:jvm的ApplicationClassLoader如何获取的Classpath

参考文献:https://blog.csdn.net/loreal8/article/details/106572817 我最近在看springBoot源码,其中对于SpringFactoriesLoader::loadSpringFactories应用classLoader加载META-INF/spring.factories外面写的类。我看META-INF/spring.factories是相对路径,所以就想晓得到底是从哪个根目录下找META-INF/spring.factories。 通过我问chatGPT和应用google搜寻,得悉是在Classpath下进行搜寻的。以下是详情: 在应用shell,执行 java 命令时,java利用作为shell的子过程,继承了shell过程的当前工作目录,并把当前工作目录加进Classpath汇合。 子过程的继承: fork出子过程后,子过程都会继承父过程以下信息;1.文件描述符2.理论用户ID,理论组ID,无效用户ID,无效组ID3.过程组ID4.增加组ID5.对话期ID6.管制终端7.设置-用户-ID标记和设置-组-ID标记8.当前工作目录9.根目录10.文件形式创立字11.信号屏蔽和排列12.对任意关上文件描述符大的在执行时敞开标记。13.环境14.链接的共享存储段15.资源限度 classLoader在加载META-INF/spring.factories时,会通过jvm的办法System.getProperty("java.class.path"),获取到calsspath,而后以classpath为根目录进行搜寻。 import org.junit.jupiter.api.Test;public class TestSomething { @Test public void testClass() { String classpath = System.getProperty("java.class.path"); System.out.println("获取Classpath为:" + classpath); }}输入为: 获取Classpath为:/home/jo/.m2/repository/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar:/home/jo/.m2/repository/org/junit/vintage/junit-vintage-engine/5.8.2/junit-vintage-engine-5.8.2.jar:/home/jo/APP/idea-IC-231.9011.34/lib/idea_rt.jar:/home/jo/APP/idea-IC-231.9011.34/plugins/junit/lib/junit5-rt.jar:/home/jo/APP/idea-IC-231.9011.34/plugins/junit/lib/junit-rt.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/charsets.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/deploy.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/cldrdata.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/dnsns.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/jaccess.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/jfxrt.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/localedata.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/nashorn.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/sunec.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/sunjce_provider.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/sunpkcs11.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/ext/zipfs.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/javaws.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/jce.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/jfr.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/jfxswt.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/jsse.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/management-agent.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/plugin.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/resources.jar:/usr/share/jdk/jdk8/jdk1.8.0_241/jre/lib/rt.jar:/home/jo/IdeaProjects/webDemo/target/test-classes:/home/jo/IdeaProjects/webDemo/target/classes:/home/jo/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.7.4/spring-boot-starter-web-2.7.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-starter/2.7.4/spring-boot-starter-2.7.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot/2.7.4/spring-boot-2.7.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.7.4/spring-boot-autoconfigure-2.7.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.7.4/spring-boot-starter-logging-2.7.4.jar:/home/jo/.m2/repository/ch/qos/logback/logback-classic/1.2.11/logback-classic-1.2.11.jar:/home/jo/.m2/repository/ch/qos/logback/logback-core/1.2.11/logback-core-1.2.11.jar:/home/jo/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.17.2/log4j-to-slf4j-2.17.2.jar:/home/jo/.m2/repository/org/apache/logging/log4j/log4j-api/2.17.2/log4j-api-2.17.2.jar:/home/jo/.m2/repository/org/slf4j/jul-to-slf4j/1.7.36/jul-to-slf4j-1.7.36.jar:/home/jo/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/home/jo/.m2/repository/org/yaml/snakeyaml/1.30/snakeyaml-1.30.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.7.4/spring-boot-starter-json-2.7.4.jar:/home/jo/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.13.4/jackson-databind-2.13.4.jar:/home/jo/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.13.4/jackson-annotations-2.13.4.jar:/home/jo/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.13.4/jackson-core-2.13.4.jar:/home/jo/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.13.4/jackson-datatype-jdk8-2.13.4.jar:/home/jo/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.13.4/jackson-datatype-jsr310-2.13.4.jar:/home/jo/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.13.4/jackson-module-parameter-names-2.13.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.7.4/spring-boot-starter-tomcat-2.7.4.jar:/home/jo/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.65/tomcat-embed-core-9.0.65.jar:/home/jo/.m2/repository/org/apache/tomcat/embed/tomcat-embed-el/9.0.65/tomcat-embed-el-9.0.65.jar:/home/jo/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.65/tomcat-embed-websocket-9.0.65.jar:/home/jo/.m2/repository/org/springframework/spring-web/5.3.23/spring-web-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-beans/5.3.23/spring-beans-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-webmvc/5.3.23/spring-webmvc-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-aop/5.3.23/spring-aop-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-context/5.3.23/spring-context-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-expression/5.3.23/spring-expression-5.3.23.jar:/home/jo/.m2/repository/org/projectlombok/lombok/1.18.24/lombok-1.18.24.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-starter-test/2.7.4/spring-boot-starter-test-2.7.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-test/2.7.4/spring-boot-test-2.7.4.jar:/home/jo/.m2/repository/org/springframework/boot/spring-boot-test-autoconfigure/2.7.4/spring-boot-test-autoconfigure-2.7.4.jar:/home/jo/.m2/repository/com/jayway/jsonpath/json-path/2.7.0/json-path-2.7.0.jar:/home/jo/.m2/repository/net/minidev/json-smart/2.4.8/json-smart-2.4.8.jar:/home/jo/.m2/repository/net/minidev/accessors-smart/2.4.8/accessors-smart-2.4.8.jar:/home/jo/.m2/repository/org/ow2/asm/asm/9.1/asm-9.1.jar:/home/jo/.m2/repository/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar:/home/jo/.m2/repository/jakarta/xml/bind/jakarta.xml.bind-api/2.3.3/jakarta.xml.bind-api-2.3.3.jar:/home/jo/.m2/repository/jakarta/activation/jakarta.activation-api/1.2.2/jakarta.activation-api-1.2.2.jar:/home/jo/.m2/repository/org/assertj/assertj-core/3.22.0/assertj-core-3.22.0.jar:/home/jo/.m2/repository/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar:/home/jo/.m2/repository/org/mockito/mockito-core/4.5.1/mockito-core-4.5.1.jar:/home/jo/.m2/repository/net/bytebuddy/byte-buddy/1.12.17/byte-buddy-1.12.17.jar:/home/jo/.m2/repository/net/bytebuddy/byte-buddy-agent/1.12.17/byte-buddy-agent-1.12.17.jar:/home/jo/.m2/repository/org/objenesis/objenesis/3.2/objenesis-3.2.jar:/home/jo/.m2/repository/org/mockito/mockito-junit-jupiter/4.5.1/mockito-junit-jupiter-4.5.1.jar:/home/jo/.m2/repository/org/skyscreamer/jsonassert/1.5.1/jsonassert-1.5.1.jar:/home/jo/.m2/repository/com/vaadin/external/google/android-json/0.0.20131108.vaadin1/android-json-0.0.20131108.vaadin1.jar:/home/jo/.m2/repository/org/springframework/spring-core/5.3.23/spring-core-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-jcl/5.3.23/spring-jcl-5.3.23.jar:/home/jo/.m2/repository/org/springframework/spring-test/5.3.23/spring-test-5.3.23.jar:/home/jo/.m2/repository/org/xmlunit/xmlunit-core/2.9.0/xmlunit-core-2.9.0.jar:/home/jo/.m2/repository/junit/junit/4.12/junit-4.12.jar:/home/jo/.m2/repository/org/hamcrest/hamcrest-core/2.2/hamcrest-core-2.2.jar:/home/jo/.m2/repository/com/alibaba/fastjson/1.2.76/fastjson-1.2.76.jar:/home/jo/.m2/repository/org/junit/jupiter/junit-jupiter/5.10.0-M1/junit-jupiter-5.10.0-M1.jar:/home/jo/.m2/repository/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar:/home/jo/.m2/repository/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar:/home/jo/.m2/repository/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar:/home/jo/.m2/repository/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar:/home/jo/.m2/repository/org/junit/jupiter/junit-jupiter-params/5.8.2/junit-jupiter-params-5.8.2.jar:/home/jo/.m2/repository/org/junit/jupiter/junit-jupiter-engine/5.8.2/junit-jupiter-engine-5.8.2.jar:/home/jo/.m2/repository/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar:/home/jo/APP/idea-IC-231.9011.34/lib/idea_rt.jar以下是我和chatGPT的对话: 重点: 我也不晓得chatGPT有没有胡说,所以,如有谬误请麻烦斧正,谢谢。

June 15, 2023 · 1 min · jiezi

Tomcat-7-类加载器是如何实现的

Tomcat 7 类加载器是如何实现的XML分析器和Java在安全管理器下运行高级配置概观 概述与许多服务器应用程序一样,Tomcat安装了各种类加载器(即实现的类java.lang.ClassLoader),以允许容器的不同部分和容器上运行的Web应用程序访问可用类和资源的不同存储库。此机制用于提供Servlet规范2.4版中定义的功能 - 特别是9.4和9.6节。 在Java环境中,类加载器排列在父子树中。通常,当要求类加载器加载特定的类或资源时,它首先将请求委托给父类加载器,然后仅在父类加载器找不到所请求的类或资源时查找它自己的存储库。 。请注意,Web应用程序类加载器的模型与此略有不同,如下所述,但主要原则是相同的。 当Tomcat启动时,它会创建一组类加载器,这些加载器被组织成以下父子关系,其中父类加载器位于子类加载器之上: Bootstrap | System | Common / \ Webapp1 Webapp2 ...这些类加载器的特征,包括它们可见的类和资源的来源,将在下一节中详细讨论。 类加载器定义如上图所示,Tomcat在初始化时创建以下类加载器: Bootstrap - 此类加载器包含Java虚拟机提供的基本运行时类,以及System Extensions目录($JAVA_HOME/jre/lib/ext)中存在的JAR文件中的任何类。 注意:一些JVM可能将其实现为多个类加载器,或者它可能根本不可见(作为类加载器)。System - 此类加载器通常从CLASSPATH环境变量的内容初始化。Tomcat内部类和Web应用程序都可以看到所有这些类。但是,标准的Tomcat启动脚本($CATALINA_HOME/bin/catalina.sh或 %CATALINA_HOME%bincatalina.bat)完全忽略CLASSPATH环境变量本身的内容,而是从以下存储库构建System类加载器:$CATALINA_HOME/bin/bootstrap.jar - 包含用于初始化 Tomcat 服务器的main() 方法,以及它依赖的类加载器实现类。$CATALINA_BASE/bin/tomcat-juli.jar 或 $CATALINA_HOME/bin/tomcat-juli.jar - 记录实现类。其中包括java.util.loggingAPI 的增强类 ,称为Tomcat JULI,以及Tomcat内部使用的Apache Commons Logging库的软件包重命名副本。有关详细信息,请参阅日志记录如果tomcat-juli.jar是出现在 $CATALINA_BASE/bin 中,它被用来代替 $CATALINA_HOME/bin 中的那一个。它在某些日志记录配置中很有用 $CATALINA_HOME/bin/commons-daemon.jar - 来自Apache Commons Daemon项目的类。这个JAR文件不存在于CLASSPATH 由脚本catalina.bat|.sh 构建,但是从bootstrap.jar的清单文件中引用。 Common - 此类加载器包含对Tomcat内部类和所有Web应用程序都可见的其他类。通常情况下,应用类应该不 放在这里。此类由common.loader加载器搜索的位置$CATALINA_BASE/conf/catalina.properties 中的属性定义。默认设置将按列出的顺序搜索以下位置: 解压缩的类和资源 $CATALINA_BASE/libJAR文件 $CATALINA_BASE/lib解压缩的类和资源 $CATALINA_HOME/libJAR文件 $CATALINA_HOME/lib默认情况下,这包括以下内容: annotations-api.jar - JavaEE注释类。catalina.jar - Tomcat的Catalina servlet容器部分的实现。catalina-ant.jar - Tomcat Catalina Ant任务。catalina-ha.jar - 高可用性包。catalina-tribes.jar - 群组通信包。ecj - * .jar - Eclipse JDT Java编译器。el-api.jar - EL 2.2 API。jasper.jar - Tomcat Jasper JSP编译器和运行时。jasper-el.jar - Tomcat Jasper EL实现。jsp-api.jar - JSP 2.2 API。servlet-api.jar - Servlet 3.0 API。tomcat-api.jar - Tomcat定义的几个接口。tomcat-coyote.jar - Tomcat连接器和实用程序类。tomcat-dbcp.jar - 基于包重命名的Apache Commons Pool和Apache Commons DBCP 1.x的数据库连接池实现。tomcat-i18n - **。jar - 包含其他语言资源包的可选JAR。由于默认捆绑包也包含在每个单独的JAR中,因此如果不需要消息的国际化,则可以安全地删除它们。tomcat-jdbc.jar - 另一种数据库连接池实现,称为Tomcat JDBC池。有关详细信息,请参阅 文档tomcat-util.jar - Apache Tomcat的各种组件使用的公共类。tomcat7-websocket.jar - WebSocket 1.1实现websocket-api.jar - WebSocket 1.1 APIWebappX - 为部署在单个Tomcat实例中的每个Web应用程序创建一个类加载器。/WEB-INF/classesWeb应用程序目录中的所有解压缩的类和资源,以及Web应用程序/WEB-INF/lib目录下的JAR文件中的类和资源,都对此Web应用程序可见,但对其他应用程序不可见。 ...

June 13, 2019 · 1 min · jiezi

ClassLoader(二)- 加载过程

本文源代码在Github。本文仅为个人笔记,不应作为权威参考。原文在前一篇文章初步了解ClassLoader里提到了委托模型(又称双亲委派模型),解释了ClassLoader hierarchy(层级)处理类加载的过程。那么class文件是如何变成Class对象的呢?Class的加载过程Class加载分为这几步:创建和加载(Creation and Loading)链接(Linking)验证(Verification)准备(Preparation)解析(Resolution),此步骤可选初始化(Initialization)注: 前面说了数组类是虚拟机直接创建的,以上过程不适用于数组类。创建和加载(Creation and Loading)何时会触发一个类的加载?Java Language Specification - 12.1.1. Load the Class Test:The initial attempt to execute the method main of class Test discovers that the class Test is not loaded - that is, that the Java Virtual Machine does not currently contain a binary representation for this class. The Java Virtual Machine then uses a class loader to attempt to find such a binary representation.也就是说,当要用到一个类,JVM发现还没有包含这个类的二进制形式(字节)时,就会使用ClassLoader尝试查找这个类的二进制形式。我们知道ClassLoader委托模型,也就是说实际触发加载的ClassLoader和真正加载的ClassLoader可能不是同一个,JVM将它们称之为initiating loader和defining loader(Java Virtual Machine Specification - 5.3. Creation and Loading):A class loader L may create C by defining it directly or by delegating to another class loader. If L creates C directly, we say that L defines C or, equivalently, that L is the defining loader of C.When one class loader delegates to another class loader, the loader that initiates the loading is not necessarily the same loader that completes the loading and defines the class. If L creates C, either by defining it directly or by delegation, we say that L initiates loading of C or, equivalently, that L is an initiating loader of C.那么当A类使用B类的时候,B类使用的是哪个ClassLoader呢?Java Virtual Machine Specification - 5.3. Creation and Loading:The Java Virtual Machine uses one of three procedures to create class or interface C denoted by N:If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).Otherwise N denotes an array class. An array class is created directly by the Java Virtual Machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.注:上文的C和D都是类,N则是C的名字。也就说如果D用到C,且C还没有被加载,且C不是数组,那么:如果D的defining loader是bootstrap class loader,那么C的initiating loader就是bootstrap class loader。如果D的defining loader是自定义的class loader X,那么C的initiating loader就是X。再总结一下就是:如果D用到C,且C还没有被加载,且C不是数组,那么C的initiating loader就是D的defining loader。用下面的代码观察一下:// 把这个项目打包然后放到/tmp目录下public class CreationAndLoading { public static void main(String[] args) throws Exception { // ucl1的parent是bootstrap class loader URLClassLoader ucl1 = new NamedURLClassLoader(“user-defined 1”, new URL[] { new URL(“file:///tmp/classloader.jar”) }, null); // ucl1是ucl2的parent URLClassLoader ucl2 = new NamedURLClassLoader(“user-defined 2”, new URL[0], ucl1); Class<?> fooClass2 = ucl2.loadClass(“me.chanjar.javarelearn.classloader.Foo”); fooClass2.newInstance(); }}public class Foo { public Foo() { System.out.println(“Foo’s classLoader: " + Foo.class.getClassLoader()); System.out.println(“Bar’s classLoader: " + Bar.class.getClassLoader()); }}public class NamedURLClassLoader extends URLClassLoader { private String name; public NamedURLClassLoader(String name, URL[] urls, ClassLoader parent) { super(urls, parent); this.name = name; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { System.out.println(“ClassLoader: " + this.name + " findClass(” + name + “)”); return super.findClass(name); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { System.out.println(“ClassLoader: " + this.name + " loadClass(” + name + “)”); return super.loadClass(name); } @Override public String toString() { return name; }}运行结果是:ClassLoader: user-defined 2 loadClass(me.chanjar.javarelearn.classloader.Foo)ClassLoader: user-defined 1 findClass(me.chanjar.javarelearn.classloader.Foo)ClassLoader: user-defined 1 loadClass(java.lang.Object)ClassLoader: user-defined 1 loadClass(java.lang.System)ClassLoader: user-defined 1 loadClass(java.lang.StringBuilder)ClassLoader: user-defined 1 loadClass(java.lang.Class)ClassLoader: user-defined 1 loadClass(java.io.PrintStream)Foo’s classLoader: user-defined 1ClassLoader: user-defined 1 loadClass(me.chanjar.javarelearn.classloader.Bar)ClassLoader: user-defined 1 findClass(me.chanjar.javarelearn.classloader.Bar)Bar’s classLoader: user-defined 1可以注意到Foo的initiating loader是user-defined 2,但是defining loader是user-defined 1。而Bar的initiating loader与defining loader则直接是user-defined 1,绕过了user-defined 2。观察结果符合预期。链接验证(Verification)验证类的二进制形式在结构上是否正确。准备(Preparation)为类创建静态字段,并且为这些静态字段初始化默认值。解析(Resolution)JVM在运行时会为每个类维护一个run-time constant pool,run-time constant pool构建自类的二进制形式里的constant_pool表。run-time constant pool里的所有引用一开始都是符号引用(symbolic reference)(见Java Virutal Machine Specification - 5.1. The Run-Time Constant Pool)。符号引用就是并非真正引用(即引用内存地址),只是指向了一个名字而已(就是字符串)。解析阶段做的事情就是将符号引用转变成实际引用)。Java Virutal Machine Specification - 5.4. Linking:This specification allows an implementation flexibility as to when linking activities (and, because of recursion, loading) take place, provided that all of the following properties are maintained:A class or interface is completely loaded before it is linked.A class or interface is completely verified and prepared before it is initialized.也就是说仅要求:一个类在被链接之前得是完全加载的。一个类在被初始化之前得是被完全验证和准备的。所以对于解析的时机JVM Spec没有作出太多规定,只说了以下JVM指令在执行之前需要解析符号引用:anewarray, checkcast_, getfield, getstatic, instanceof, invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, ldc, ldc_w, multianewarray, new, _putfield 和 putstatic 。看不懂没关系,大致意思就是,用到字段、用到方法、用到静态方法、new类等时候需要解析符号引用。初始化如果直接赋值的静态字段被 final 所修饰,并且它的类型是基本类型或字符串时,那么该字段便会被 Java 编译器标记成常量值(ConstantValue),其初始化直接由 Java 虚拟机完成。除此之外的直接赋值操作,以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命为 <clinit>(class init)。JVM 规范枚举了下述类的初始化时机是:当虚拟机启动时,初始化用户指定的主类;new 某个类的时候调用某类的静态方法时访问某类的静态字段时子类初始化会触发父类初始化用反射API对某个类进行调用时一个接口定义了default方法(原文是non-abstract、non-static方法),某个实现了这个接口的类被初始化,那么这个接口也会被初始化初次调用 MethodHandle 实例时注意:这里没有提到new 数组的情况,所以new 数组的时候不会初始化类。同时类的初始化过程是线程安全的,下面是一个利用上述时机4和线程安全特性做的延迟加载的Singleton的例子:public class Singleton { private Singleton() {} private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return LazyHolder.INSTANCE; }}这种做法被称为Initialization-on-demand holder idiom。类加载常见异常ClassNotFoundExceptionJava Virutal Machine Specification - 5.3.1. Loading Using the Bootstrap Class Loader:If no purported representation of C is found, loading throws an instance of ClassNotFoundException.Java Virutal Machine Specification - 5.3.2. Loading Using a User-defined Class Loader:When the loadClass method of the class loader L is invoked with the name N of a class or interface C to be loaded, L must perform one of the following two operations in order to load C:The class loader L can create an array of bytes representing C as the bytes of a ClassFile structure (§4.1); it then must invoke the method defineClass of class ClassLoader. Invoking defineClass causes the Java Virtual Machine to derive a class or interface denoted by N using L from the array of bytes using the algorithm found in §5.3.5.The class loader L can delegate the loading of C to some other class loader L’. This is accomplished by passing the argument N directly or indirectly to an invocation of a method on L’ (typically the loadClass method). The result of the invocation is C.In either (1) or (2), if the class loader L is unable to load a class or interface denoted by N for any reason, it must throw an instance of ClassNotFoundException.所以,ClassNotFoundException发生在【加载阶段】:如果用的是bootstrap class loader,则当找不到其该类的二进制形式时抛出ClassNotFoundException如果用的是用户自定义class loader,不管是自己创建二进制(这里包括从文件读取或者内存中创建),还是代理给其他class loader,只要出现无法加载的情况,都要抛出ClassNotFoundExceptionNoClassDefFoundErrorJava Virtual Machine Specification - 5.3. Creation and LoadingIf the Java Virtual Machine ever attempts to load a class C during verification (§5.4.1) or resolution (§5.4.3) (but not initialization (§5.5)), and the class loader that is used to initiate loading of C throws an instance of ClassNotFoundException, then the Java Virtual Machine must throw an instance of NoClassDefFoundError whose cause is the instance of ClassNotFoundException.(A subtlety here is that recursive class loading to load superclasses is performed as part of resolution (§5.3.5, step 3). Therefore, a ClassNotFoundException that results from a class loader failing to load a superclass must be wrapped in a NoClassDefFoundError.)Java Virtual Machine Specification - 5.3.5. Deriving a Class from a class File RepresentationOtherwise, if the purported representation does not actually represent a class named N, loading throws an instance of NoClassDefFoundError or an instance of one of its subclasses.Java Virtual Machine Specification - 5.5. InitializationIf the Class object for C is in an erroneous state, then initialization is not possible. Release LC and throw a NoClassDefFoundError.所以,NoClassDefFoundError发生在:【加载阶段】,因其他类的【验证】or【解析】触发对C类的【加载】,此时发生了ClassNotFoundException,那么就要抛出NoClassDefFoundError,cause 是ClassNotFoundException。【加载阶段】,在【解析】superclass的过程中发生的ClassNotFoundException也必须包在NoClassDefFoundError里。【加载阶段】,发现找到的二进制里的类名和要找的类名不一致时,抛出NoClassDefFoundError【初始化阶段】,如果C类的Class对象处于错误状态,那么抛出NoClassDefFoundError追踪类的加载可以在JVM启动时添加-verbose:class来打印类加载过程。参考资料Java Language Specification - Chapter 12. ExecutionJava Virtual Machine Specification - Chapter 5. Loading, Linking, and Initializing极客时间 - 深入拆解Java虚拟机 - 03 Java虚拟机是如何加载Java类的?(专栏文章,需付费购买)CS-Note 类加载机制深入理解JVM(八)——类加载的时机深入理解JVM(九)——类加载的过程 ...

February 20, 2019 · 5 min · jiezi

ClassLoader(一)- 介绍

本文源代码在Github。本文仅为个人笔记,不应作为权威参考。原文什么是ClassLoaderjavadoc ClassLoader:A class loader is an object that is responsible for loading classes. …Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system.简单来说:ClassLoader是一个负责加载Class的对象。给ClassLoader一个类名(需符合Java语言规范),那么它就应该尝试定位,或者生成包含该类定义的数据。一个典型的定位策略是把类名转换成class文件名,然后从文件系统里读取这个class文件。三种ClassLoader实现讲到bootstrap class loader就不得不说三种常见的ClassLoader实现。执行下面代码会看到三种类型的ClassLoader实现:import com.sun.javafx.util.Logging;import java.util.ArrayList;public class PrintClassLoader { public static void main(String[] args) { System.out.println(“Classloader for ArrayList: " + ArrayList.class.getClassLoader()); System.out.println(“Classloader for Logging: " + Logging.class.getClassLoader()); System.out.println(“Classloader for this class: " + PrintClassLoader.class.getClassLoader()); }}结果如下:Classloader for ArrayList: nullClassloader for Logging: sun.misc.Launcher$ExtClassLoader@5e2de80cClassloader for this class: sun.misc.Launcher$AppClassLoader@18b4aac2Bootstrap class loader。bootstrap class loader是native code写的。它是所有ClassLoader的祖先,它是顶级ClassLoader。它负责加载JDK的内部类型,一般来说就是位于$JAVA_HOME/jre/lib下的核心库和rt.jar。Extension class loader。即Extension class loader,负责加载Java核心类的扩展,加载$JAVA_HOME/lib/ext目录和System Property java.ext.dirs所指定目录下的类(见Java Extension Mechanism Architecture)。System class loader,又称Application class loader。它的parent class loader是extension class loader(可以从sun.misc.Launcher的构造函数里看到),负责加载CLASSPATH环境变量、-classpath/-cp启动参数指定路径下的类。类的ClassLoader每个Class对象引用了当初加载自己的ClassLoader(javadoc ClassLoader):Every Class object contains a reference to the ClassLoader that defined it.其实Class对象的getClassLoader()方法就能够得到这个ClassLoader,并且说了如果该方法返回空,则说明此Class对象是被bootstrap class loader加载的,见getClassLoader() javadoc:Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.数组类的ClassLoaderClass objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.简单来说说了三点:数组也是类,但是它的Class对象不是由ClassLoader创建的,而是由Java runtime根据需要自动创建的。数组的getClassLoader()的结果同其元素类型的ClassLoader如果元素是基础类型,则数组类没有ClassLoader下面是一段实验代码:import com.sun.javafx.util.Logging;public class PrintArrayClassLoader { public static void main(String[] args) { System.out.println(“ClassLoader for int[]: " + new int[0].getClass().getClassLoader()); System.out.println(“ClassLoader for string[]: " + new String[0].getClass().getClassLoader()); System.out.println(“ClassLoader for Logging[]: " + new Logging[0].getClass().getClassLoader()); System.out.println(“ClassLoader for this class[]: " + new PrintArrayClassLoader[0].getClass().getClassLoader()); }}得到的结果如下,符合上面的说法:ClassLoader for int[]: nullClassLoader for string[]: nullClassLoader for Logging[]: sun.misc.Launcher$ExtClassLoader@5e2de80cClassLoader for this class[]: sun.misc.Launcher$AppClassLoader@18b4aac2那如果是二维数组会怎样呢?下面是实验代码:import com.sun.javafx.util.Logging;public class PrintArrayArrayClassLoader { public static void main(String[] args) { System.out.println(“ClassLoader for int[][]: " + new int[0][].getClass().getClassLoader()); System.out.println(“ClassLoader for string[][]: " + new String[0][].getClass().getClassLoader()); System.out.println(“ClassLoader for Logging[][]: " + new Logging[0][].getClass().getClassLoader()); System.out.println(“ClassLoader for this class[][]: " + new PrintArrayClassLoader[0][].getClass().getClassLoader()); System.out.println(“ClassLoader for this Object[][] of this class[]: " + new Object[][]{new PrintArrayArrayClassLoader[0]}.getClass().getClassLoader()); }}结果是:ClassLoader for int[][]: nullClassLoader for string[][]: nullClassLoader for Logging[][]: sun.misc.Launcher$ExtClassLoader@5e2de80cClassLoader for this class[][]: sun.misc.Launcher$AppClassLoader@18b4aac2ClassLoader for this Object[][] of this class[]: null注意第四行的结果,我们构建了一个Object[][],里面放的是PrintArrayArrayClassLoader[],但结果依然是null。所以:二维数组的ClassLoader和其定义的类型(元素类型)的ClassLoader相同。与其实际内部存放的类型无关。ClassLoader类的ClassLoaderClassLoader本身也是类,那么是谁加载它们的呢?实际上ClassLoader类的ClassLoader就是bootstrap class loader。下面是实验代码:import com.sun.javafx.util.Logging;public class PrintClassLoaderClassLoader { public static void main(String[] args) { // Launcher$ExtClassLoader System.out.println(“ClassLoader for Logging’s ClassLoader: " + Logging.class.getClassLoader().getClass().getClassLoader()); // Launcher$AppClassLoader System.out.println(“ClassLoader for this class’s ClassLoader: " + PrintClassLoaderClassLoader.class.getClassLoader().getClass().getClassLoader()); // 自定义ClassLoader System.out.println(“ClassLoader for custom ClassLoader: " + DummyClassLoader.class.getClassLoader().getClass().getClassLoader()); } public static class DummyClassLoader extends ClassLoader { }}结果是:ClassLoader for Logging’s ClassLoader: nullClassLoader for this class’s ClassLoader: nullClassLoader for custom ClassLoader: nullClassLoader解决了什么问题简单来说ClassLoader就是解决类加载问题的,当然这是一句废话。JDK里的ClassLoader是一个抽象类,这样做的目的是能够让应用开发者定制自己的ClassLoader实现(比如添加解密/加密)、动态插入字节码等,我认为这才是ClassLoader存在的最大意义。ClassLoader的工作原理还是看javadoc的说法:The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine’s built-in class loader, called the “bootstrap class loader”, does not itself have a parent but may serve as the parent of a ClassLoader instance.简单来说:ClassLoader使用委托模型(国内普遍称之为双亲委派模型)查找Class或Resource。每个 ClassLoader 实例都有一个parent ClassLoader。当要查找Class或者Resource的时候,递归委托给parent,如果parent找不到,才会自己找。举例说明:如果ClassLoader层级关系是这样A->B->C,如果被查找Class只能被A找到,那么过程是A-delegate->B-delegate->C(not found)->B(not found)->A(found)。JVM有一个内置的顶级ClassLoader,叫做bootstrap class loader,它没有parent,它是老祖宗。ContextClassLoaderClassLoader的委托模型存在这么一个问题:子ClassLoader能够看见父ClassLoader所加载的类,而父ClassLoader看不到子ClassLoader所加载的类。这个问题出现在Java提供的SPI上,简单举例说明:Java核心库提供了SPI A尝试提供了自己的实现 BSPI A尝试查找实现B,结果找不到这是因为B一般都是在Classpath中的,它是被System class loader加载的,而SPI A是在核心库里的,它是被bootstrap class loader加载的,而bootstrap class loader是顶级ClassLoader,它不能向下委托给System class loader,所以SPI A是找不到实现B的。这个时候可以通过java.lang.Thread#getContextClassLoader()和java.lang.Thread#setContextClassLoader来让SPI A加载到B。为何SPI A不直接使用System class loader来加载呢?我想这是因为如果写死了System class loader那就缺少灵活性的关系吧。Class的唯一性如果一个类被一个ClassLoader加载两次,那么两次的结果应该是一致的,并且这个加载过程是线程安全的,见ClassLoader.java源码:protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { // … try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. // … c = findClass(name); // … } } // … return c; }}如果一个类被两个不同的ClassLoader加载会怎样呢?看下面代码:// 把这个项目打包然后放到/tmp目录下public class ClassUniqueness { public static void main(String[] args) throws Exception { Class<?> fooClass1 = Class.forName(“me.chanjar.javarelearn.classloader.ClassUniqueness”); System.out.println(“1st ClassUniqueness’s ClassLoader: " + fooClass1.getClassLoader()); // 故意将parent class loader设置为null,否则就是SystemClassLoader(即ApplicationClassLoader) URLClassLoader ucl = new URLClassLoader(new URL[] { new URL(“file:///tmp/classloader.jar”) }, null); Class<?> fooClass2 = ucl.loadClass(“me.chanjar.javarelearn.classloader.ClassUniqueness”); System.out.println(“2nd ClassUniqueness’s ClassLoader: " + fooClass2.getClassLoader()); System.out.println(“Two ClassUniqueness class equals? " + fooClass1.equals(fooClass2)); }}运行结果是:1st ClassUniqueness’s ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac22nd ClassUniqueness’s ClassLoader: java.net.URLClassLoader@66d3c617Two ClassUniqueness class equals? false```观察到两点:虽然是同一个类,但是加载它们的ClassLoader不同。虽然是同一个类,但是它们并不相等。由此可以得出结论:一个Class的唯一性不仅仅是其全限定名(Fully-qualified-name),而是由【加载其的ClassLoader + 其全限定名】联合保证唯一。这种机制对于解决诸如类冲突问题非常有用,类冲突问题就是在运行时存在同一个类的两个不同版本,同时代码里又都需要使用这两个不同版本的类。解决这个问题的思路就是使用不同的ClassLoader加载这两个版本的类。事实上OSGi或者Web容器就是这样做的(它们不是严格遵照委托模型,而是先自己找,找不到了再委托给parent ClassLoader)。参考文档JDK Javadoc - ClassLoaderJDK Javadoc - ClassJava虚拟机是如何加载Java类的?(极客时间专栏,需付费购买)Class Loaders in Java深入探讨Java类加载器Java Language Specification - Chapter 12. ExecutionJava Virtual Machine Specification - Chapter 5. Loading, Linking, and Initializing ...

February 20, 2019 · 4 min · jiezi

关于ClassLoader的学习笔记,详解版

ClassLoader 详解ClassLoader 做什么的?延迟加载各司其职ClassLoader 传递性双亲委派Class.forName自定义加载器Class.forName vs ClassLoader.loadClass钻石依赖分工与合作Thread.contextClassLoaderClassLoader 是 Java 届最为神秘的技术之一,无数人被它伤透了脑筋,摸不清门道究竟在哪里。网上的文章也是一篇又一篇,经过本人的亲自鉴定,绝大部分内容都是在误导别人。本文我带读者彻底吃透 ClassLoader,以后其它的相关文章你们可以不必再细看了。ClassLoader 做什么的?顾名思义,它是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。字节码可以来自于磁盘文件 .class,也可以是 jar 包里的 .class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。有很多字节码加密技术就是依靠定制 ClassLoader 来实现的。先使用工具对字节码文件进行加密,运行时使用定制的 ClassLoader 先解密文件内容再加载这些解密后的字节码。每个 Class 对象的内部都有一个 classLoader 字段来标识自己是由哪个 ClassLoader 加载的。ClassLoader 就像一个容器,里面装了很多已经加载的 Class 对象。class Class<T> { … private final ClassLoader classLoader; …}延迟加载JVM 运行并不是一次性加载所需要的全部类的,它是按需加载,也就是延迟加载。程序在运行的过程中会逐渐遇到很多不认识的新类,这时候就会调用 ClassLoader 来加载这些类。加载完成后就会将 Class 对象存在 ClassLoader 里面,下次就不需要重新加载了。比如你在调用某个类的静态方法时,首先这个类肯定是需要被加载的,但是并不会触及这个类的实例字段,那么实例字段的类别 Class 就可以暂时不必去加载,但是它可能会加载静态字段相关的类别,因为静态方法会访问静态字段。而实例字段的类别需要等到你实例化对象的时候才可能会加载。各司其职JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。JVM 中内置了三个重要的 ClassLoader,分别是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。BootstrapClassLoader 负责加载 JVM 运行时核心类,这些类位于 ExtensionClassLoader 负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVAHOME/lib/rt.jar文件中,我们常用内置库[java.xxx](https://link.juejin.im?target…,比如swing系列、内置的js引擎、xml解析器等等,这些库名通常以javax开头,它们的jar包位于JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。AppClassLoader 才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。那些位于网络上静态文件服务器提供的 jar 包和 class文件,jdk 内置了一个 URLClassLoader,用户只需要传递规范的网络路径给构造器,就可以使用 URLClassLoader 来加载远程类库了。URLClassLoader 不但可以加载远程类库,还可以加载本地路径的类库,取决于构造器中不同的地址形式。ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子类,它们都是从本地文件系统里加载类库。AppClassLoader 可以由 ClassLoader 类提供的静态方法 getSystemClassLoader() 得到,它就是我们所说的「系统类加载器」,我们用户平时编写的类代码通常都是由它加载的。当我们的 main 方法执行的时候,这第一个用户类的加载器就是 AppClassLoader。ClassLoader 传递性程序在运行过程中,遇到了一个未知的类,它会选择哪个 ClassLoader 来加载它呢?虚拟机的策略是使用调用者 Class 对象的 ClassLoader 来加载当前未知的类。何为调用者 Class 对象?就是在遇到这个未知的类时,虚拟机肯定正在运行一个方法调用(静态方法或者实例方法),这个方法挂在哪个类上面,那这个类就是调用者 Class 对象。前面我们提到每个 Class 对象里面都有一个 classLoader 属性记录了当前的类是由谁来加载的。因为 ClassLoader 的传递性,所有延迟加载的类都会由初始调用 main 方法的这个 ClassLoader 全全负责,它就是 AppClassLoader。双亲委派前面我们提到 AppClassLoader 只负责加载 Classpath 下面的类库,如果遇到没有加载的系统类库怎么办,AppClassLoader 必须将系统类库的加载工作交给 BootstrapClassLoader 和 ExtensionClassLoader 来做,这就是我们常说的「双亲委派」。AppClassLoader 在加载一个未知的类名时,它并不是立即去搜寻 Classpath,它会首先将这个类名称交给 ExtensionClassLoader 来加载,如果 ExtensionClassLoader 可以加载,那么 AppClassLoader 就不用麻烦了。否则它就会搜索 Classpath 。而 ExtensionClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 BootstrapClassLoader 来加载,如果 BootstrapClassLoader 可以加载,那么 ExtensionClassLoader 也就不用麻烦了。否则它就会搜索 ext 路径下的 jar 包。这三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器。class ClassLoader { … private final ClassLoader parent; …}值得注意的是图中的 ExtensionClassLoader 的 parent 指针画了虚线,这是因为它的 parent 的值是 null,当 parent 字段是 null 时就表示它的父加载器是「根加载器」。如果某个 Class 对象的 classLoader 属性值是 null,那么就表示这个类也是「根加载器」加载的。注意这里的 parent 不是 super 不是父类,只是 ClassLoader 内部的字段。Class.forName当我们在使用 jdbc 驱动时,经常会使用 Class.forName 方法来动态加载驱动类。Class.forName(“com.mysql.cj.jdbc.Driver”);其原理是 mysql 驱动的 Driver 类里有一个静态代码块,它会在 Driver 类被加载的时候执行。这个静态代码块会将 mysql 驱动实例注册到全局的 jdbc 驱动管理器里。class Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException(“Can’t register driver!”); } } …}forName 方法同样也是使用调用者 Class 对象的 ClassLoader 来加载目标类。不过 forName 还提供了多参数版本,可以指定使用哪个 ClassLoader 来加载Class<?> forName(String name, boolean initialize, ClassLoader cl)通过这种形式的 forName 方法可以突破内置加载器的限制,通过使用自定类加载器允许我们自由加载其它任意来源的类库。根据 ClassLoader 的传递性,目标类库传递引用到的其它类库也将会使用自定义加载器加载。自定义加载器ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass()。loadClass() 方法是加载目标类的入口,它首先会查找当前 ClassLoader 以及它的双亲里面是否已经加载了目标类,如果没有找到就会让双亲尝试加载,如果双亲都加载不了,就会调用 findClass() 让自定义加载器自己来加载目标类。ClassLoader 的 findClass() 方法是需要子类来覆盖的,不同的加载器将使用不同的逻辑来获取目标类的字节码。拿到这个字节码之后再调用 defineClass() 方法将字节码转换成 Class 对象。下面我使用伪代码表示一下基本过程class ClassLoader { // 加载入口,定义了双亲委派规则 Class loadClass(String name) { // 是否已经加载了 Class t = this.findFromLoaded(name); if(t == null) { // 交给双亲 t = this.parent.loadClass(name) } if(t == null) { // 双亲都不行,只能靠自己了 t = this.findClass(name); } return t; } // 交给子类自己去实现 Class findClass(String name) { throw ClassNotFoundException(); } // 组装Class对象 Class defineClass(byte[] code, String name) { return buildClassFromCode(code, name); }}class CustomClassLoader extends ClassLoader { Class findClass(String name) { // 寻找字节码 byte[] code = findCodeFromSomewhere(name); // 组装Class对象 return this.defineClass(code, name); }}自定义类加载器不易破坏双亲委派规则,不要轻易覆盖 loadClass 方法。否则可能会导致自定义加载器无法加载内置的核心类库。在使用自定义加载器时,要明确好它的父加载器是谁,将父加载器通过子类的构造器传入。如果父类加载器是 null,那就表示父加载器是「根加载器」。// ClassLoader 构造器protected ClassLoader(String name, ClassLoader parent);双亲委派规则可能会变成三亲委派,四亲委派,取决于你使用的父加载器是谁,它会一直递归委派到根加载器。Class.forName vs ClassLoader.loadClass这两个方法都可以用来加载目标类,它们之间有一个小小的区别,那就是 Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错。Class<?> x = Class.forName("[I");System.out.println(x);x = ClassLoader.getSystemClassLoader().loadClass("[I");System.out.println(x);———————class [IException in thread “main” java.lang.ClassNotFoundException: [I…钻石依赖项目管理上有一个著名的概念叫着「钻石依赖」,是指软件依赖导致同一个软件包的两个版本需要共存而不能冲突。我们平时使用的 maven 是这样解决钻石依赖的,它会从多个冲突的版本中选择一个来使用,如果不同的版本之间兼容性很糟糕,那么程序将无法正常编译运行。Maven 这种形式叫「扁平化」依赖管理。使用 ClassLoader 可以解决钻石依赖问题。不同版本的软件包使用不同的 ClassLoader 来加载,位于不同 ClassLoader 中名称一样的类实际上是不同的类。下面让我们使用 URLClassLoader 来尝试一个简单的例子,它默认的父加载器是 AppClassLoader$ cat ~/source/jcl/v1/Dep.javapublic class Dep { public void print() { System.out.println(“v1”); }}$ cat ~/source/jcl/v2/Dep.javapublic class Dep { public void print() { System.out.println(“v1”); }}$ cat ~/source/jcl/Test.javapublic class Test { public static void main(String[] args) throws Exception { String v1dir = “file:///Users/qianwp/source/jcl/v1/”; String v2dir = “file:///Users/qianwp/source/jcl/v2/”; URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)}); URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)}); Class<?> depv1Class = v1.loadClass(“Dep”); Object depv1 = depv1Class.getConstructor().newInstance(); depv1Class.getMethod(“print”).invoke(depv1); Class<?> depv2Class = v2.loadClass(“Dep”); Object depv2 = depv2Class.getConstructor().newInstance(); depv2Class.getMethod(“print”).invoke(depv2); System.out.println(depv1Class.equals(depv2Class)); }}在运行之前,我们需要对依赖的类库进行编译$ cd ~/source/jcl/v1$ javac Dep.java$ cd ~/source/jcl/v2$ javac Dep.java$ cd ~/source/jcl$ javac Test.java$ java Testv1v2false在这个例子中如果两个 URLClassLoader 指向的路径是一样的,下面这个表达式还是 false,因为即使是同样的字节码用不同的 ClassLoader 加载出来的类都不能算同一个类depv1Class.equals(depv2Class)我们还可以让两个不同版本的 Dep 类实现同一个接口,这样可以避免使用反射的方式来调用 Dep 类里面的方法。Class<?> depv1Class = v1.loadClass(“Dep”);IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance();depv1.print()ClassLoader 固然可以解决依赖冲突问题,不过它也限制了不同软件包的操作界面必须使用反射或接口的方式进行动态调用。Maven 没有这种限制,它依赖于虚拟机的默认懒惰加载策略,运行过程中如果没有显示使用定制的 ClassLoader,那么从头到尾都是在使用 AppClassLoader,而不同版本的同名类必须使用不同的 ClassLoader 加载,所以 Maven 不能完美解决钻石依赖。 如果你想知道有没有开源的包管理工具可以解决钻石依赖的,我推荐你了解一下 sofa-ark,它是蚂蚁金服开源的轻量级类隔离框架。分工与合作这里我们重新理解一下 ClassLoader 的意义,它相当于类的命名空间,起到了类隔离的作用。位于同一个 ClassLoader 里面的类名是唯一的,不同的 ClassLoader 可以持有同名的类。ClassLoader 是类名称的容器,是类的沙箱。不同的 ClassLoader 之间也会有合作,它们之间的合作是通过 parent 属性和双亲委派机制来完成的。parent 具有更高的加载优先级。除此之外,parent 还表达了一种共享关系,当多个子 ClassLoader 共享同一个 parent 时,那么这个 parent 里面包含的类可以认为是所有子 ClassLoader 共享的。这也是为什么 BootstrapClassLoader 被所有的类加载器视为祖先加载器,JVM 核心类库自然应该被共享。Thread.contextClassLoader如果你稍微阅读过 Thread 的源代码,你会在它的实例字段中发现有一个字段非常特别class Thread { … private ClassLoader contextClassLoader; public ClassLoader getContextClassLoader() { return contextClassLoader; } public void setContextClassLoader(ClassLoader cl) { this.contextClassLoader = cl; } …}contextClassLoader「线程上下文类加载器」,这究竟是什么东西?首先 contextClassLoader 是那种需要显示使用的类加载器,如果你没有显示使用它,也就永远不会在任何地方用到它。你可以使用下面这种方式来显示使用它Thread.currentThread().getContextClassLoader().loadClass(name);这意味着如果你使用 forName(string name) 方法加载目标类,它不会自动使用 contextClassLoader。那些因为代码上的依赖关系而懒惰加载的类也不会自动使用 contextClassLoader来加载。其次线程的 contextClassLoader 默认是从父线程那里继承过来的,所谓父线程就是创建了当前线程的线程。程序启动时的 main 线程的 contextClassLoader 就是 AppClassLoader。这意味着如果没有人工去设置,那么所有的线程的 contextClassLoader 都是 AppClassLoader。那这个 contextClassLoader 究竟是做什么用的?我们要使用前面提到了类加载器分工与合作的原理来解释它的用途。它可以做到跨线程共享类,只要它们共享同一个 contextClassLoader。父子线程之间会自动传递 contextClassLoader,所以共享起来将是自动化的。如果不同的线程使用不同的 contextClassLoader,那么不同的线程使用的类就可以隔离开来。如果我们对业务进行划分,不同的业务使用不同的线程池,线程池内部共享同一个 contextClassLoader,线程池之间使用不同的 contextClassLoader,就可以很好的起到隔离保护的作用,避免类版本冲突。如果我们不去定制 contextClassLoader,那么所有的线程将会默认使用 AppClassLoader,所有的类都将会是共享的。线程的 contextClassLoader 使用场合比较罕见,如果上面的逻辑晦涩难懂也不必过于计较。JDK9 增加了模块功能之后对类加载器的结构设计做了一定程度的修改,不过类加载器的原理还是类似的,作为类的容器,它起到类隔离的作用,同时还需要依靠双亲委派机制来建立不同的类加载器之间的合作关系。完 ...

January 19, 2019 · 3 min · jiezi