什么是类加载器
类加载器就是加载磁盘中的字节码文件(.class)的到内存中的类;
类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
——《深入理解Java虚拟机》
类加载器可以说是Java语言的一项创新,也是Java语言流行的重要原因之一,它最初是为了满足Java Applet的需求而开发出来的。虽然目前Java Applet技术基本上已经“死掉”,但类加载器却在类层次划分、OSGi、热部署、代码加密等领域大放异彩,成为了Java技术体系中一块重要的基石,可谓是失之桑榆,收之东隅。
——《深入理解Java虚拟机》
类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类,都拥有一个独立的类名称空间。这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
——《深入理解Java虚拟机》
类加载器种类
BootstrapClassLoader
启动类加载器/根类加载器
负责将 <Java_Home>/jre/lib
下面的类库加载到内存中(比如/jre/lib/rt.jar
)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。不继承自java.lang.ClassLoader
。
也加载-Xbootclasspath
参数指定路径,但是必须是虚拟机识别的(仅按照文件名识别),否则就算放在/lib目录下,也不会被根类加载器加载。
根类加载器使用C++实现(HotSpot),是虚拟机的一部分。是唯一一个使用本地代码编写的加载器。
之前看到一个网友提问:“既然所有的Java类,都是由类加载器加载的,那么BootstrapClassLoader,首先是一个类,它是由谁来加载的呢?”。
其实BootstrapClassLoader
,不是一个java类。我们在IDE工具中,也引用不了该类。它是在JVM启动时运行的一个特殊的系统类加载器。
由于BootstrapClassLoader对Java不可见,所以返回了null,我们也可以通过某一个类的加载器是否为null来作为判断该类是不是使用BootstrapClassLoader进行加载的依据
图:BootstrapClassLoader不是一个java类
-Xbootclasspath参数
参见:oracle -X Command-line Options
-Xbootclasspath
This option specifies a semicolon-separated list of directories, JAR archives, and ZIP archives to search for boot class files. These are used in place of the boot class files included in the Java 2 SDK.
Note: Applications that use this option to override a class in rt.jar should not be deployed. Doing so would contravene the Java 2 Runtime Environment binary code license.
Operation
Format: -Xbootclasspath <directories and zips/jars separated by ; (Windows) or : (Linux)>
Enter this option at startup to create the default classpath for bootstrap classes and resources. This option must be entered in lower case, not camel notation, as shown in the example.
通过阅读oracle的官方文档,我们得知:-Xbootclasspath
指令用于指定用来代替java2 SDK
中包含的引导类文件的、已分号分隔(windows ; linux :)的目录。不能重写rt.jar
中的类。而且这个选项必须输入全小写字母,而不是驼峰格式的词汇。
jdk8中,系统定义的包有:
charsets.jar
deploy.jar
javaws.jar
jce.jar
jfr.jar
jfxswt.jar
jsse.jar
management-agent.jar
plugin.jar
resources.jar
rt.jar
ExtClassLoader
扩展类加载器
继承自java.lang.ClassLoader
加载<JAVA_HOME>/jre/lib/ext
目录下的类,或者被java.ext.dirs
系统变量指定的路径中的所有类库。它的父加载器是null
(因为BootstrapClassLoader是使用C++实现的,没有对应的java类)
jdk8中,系统定义的包有:
cldrdata.jar
dnsns.jar
jaccess.jar
jfxrt.jar
localedata.jar
nashorn.jar
sunec.jar
sunjce_provider.jar
sunpkcs11.jar
zipfs.jar
AppClassLoader
应用程序类加载器,又称系统类加载器。
是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般称为系统(System)加载器。
双亲委派模型
- 意义:避免重复加载,避免不安全因素(如果不采用这种机制,那么系统核心的类就可以被随意替换)
概念
即一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,
如果父类加载器还存在父类加载器,就继续向上委托,直到顶层的启动类加载器。
如果父类加载器能够完成类加载,就成功返回,
如果父类加载器无法完成加载,那么子加载器才会尝试自己去加载
图:双亲委派模型
附加源码
/**
* <pre>
* 程序目的:1. 分析不同的Java类(包括接口),是由哪一种类加载器进行加载的。
* 2. 验证 BootstrapClassLoader 启动类/根类加载器,加载的目录是"<JAVA_HOME>/lib"还是"<JAVA_HOME>/jre/lib"
*
* 验证步骤:
* 1. 获取<JAVA_HOME>/jre/lib下的类,打印查看该类是由哪一种类加载器加载的
* 2. 获取<JAVA_HOME>/lib下的类,打印查看该类是由哪一种类加载器加载的
* </pre>
* created at 2020-06-09 07:00
* @author lerry
*/
public class ClassLoaderDemo {
public static void main(String[] args) {
// BootstrapClassLoader
System.out.println("查看<JAVA_HOME>/jre/lib 下的类,使用的是哪种类加载器");
System.out.printf("/jre/lib/charsets.jar 中的 X11GB2312 的类加载器是:%s\n", X11GB2312.class.getClassLoader());
System.out.printf("/jre/lib/rt.jar 中的 Object 的类加载器是:%s\n", Object.class.getClassLoader());
System.out.printf("/jre/lib/rt.jar 中的 BigDecimal 的类加载器是:%s\n", BigDecimal.class.getClassLoader());
System.out.println("------------------------------------------------------");
// ExtClassLoader
System.out.println("查看<JAVA_HOME>/jre/lib/ext 下的类,使用的是哪种类加载器");
ClassLoader extClassLoader = FormatData_aa.class.getClassLoader();
System.out.printf("/jre/lib/ext/cldrdata.jar 中的 FormatData_aa 的类加载器是:%s\n", extClassLoader);
System.out.println("------------------------------------------------------");
// AppClassLoader
System.out.println("查看开发者自定义的类,使用的是哪种类加载器");
ClassLoader appClassLoader = ClassLoaderDemo.class.getClassLoader();
System.out.printf("自己写的类使用的类加载器是:%s\n", appClassLoader);
System.out.println("------------------------------------------------------");
// 演示双亲委派模式
while (appClassLoader != null) {
System.out.printf("目前还不是最顶级的类加载器:%s,继续执行getParent()方法获取父类加载器\n", appClassLoader);
appClassLoader = appClassLoader.getParent();
}
System.out.println("------------------------------------------------------");
System.out.println("查看<JAVA_HOME>/lib下的类,使用的是哪种类加载器");
System.out.printf("/lib/tools.jar 中的 Datatype 的类加载器是:%s\n", Datatype.class.getClassLoader());
System.out.printf("/lib/ant-javafx.jar 中的 LauncherUserJvmOptions 的类加载器是:%s\n", LauncherUserJvmOptions.class.getClassLoader());
}
}
图:<JAVA_HOME>/lib包tools.jar
图:<JAVA_HOME>/lib包ant-javafx.jar
图:/lib/tools.jar中的Datatype接口
图:/lib/ant-javafx.jar中的LauncherUserJvmOptions类
运行结果
查看<JAVA_HOME>/jre/lib 下的类,使用的是哪种类加载器
/jre/lib/charsets.jar 中的 X11GB2312 的类加载器是:null
/jre/lib/rt.jar 中的 Object 的类加载器是:null
/jre/lib/rt.jar 中的 BigDecimal 的类加载器是:null
------------------------------------------------------
查看<JAVA_HOME>/jre/lib/ext 下的类,使用的是哪种类加载器
/jre/lib/ext/cldrdata.jar 中的 FormatData_aa 的类加载器是:sun.misc.Launcher$ExtClassLoader@548c4f57
------------------------------------------------------
查看开发者自定义的类,使用的是哪种类加载器
自己写的类使用的类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
------------------------------------------------------
目前还不是最顶级的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2,继续执行getParent()方法获取父类加载器
目前还不是最顶级的类加载器:sun.misc.Launcher$ExtClassLoader@548c4f57,继续执行getParent()方法获取父类加载器
------------------------------------------------------
查看<JAVA_HOME>/lib下的类,使用的是哪种类加载器
/lib/tools.jar 中的 Datatype 的类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
/lib/ant-javafx.jar 中的 LauncherUserJvmOptions 的类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
源码运行结论
/lib/tools.jar 中的 Datatype 和 /lib/ant-javafx.jar 中的 LauncherUserJvmOptio
的类加载器是AppClassLoader,
而 /jre/lib/rt.jar 中的 BigDecimal 的类加载器是 BootstrapClassLoader。
通过验证得知,<JAVA_HOME>/jre/lib目录下的类,是由BootstrapClassLoader(启动类加载器)加载的。
启动类加载器和扩展类加载器,加载java运行时环境用到的类(<JAVA_HOME>/jre目录下)
环境说明
- jdk version:
1.8.0_144
- OS:
macOS High Sierra 10.13.4