双亲委托类加载机制
当我们使用java命令运行一个类时,并不是简单的运行的.class文件,把类加载到内存中,而是经过一系列的判断之后才加载类。以下将介绍Java的双亲委托类加载机制。
1. 加载器分类
- 启动类加载器(Aootstrap Class Loader)
由C++编写,用来加载Java的核心库文件,主要是将JDK中jre/lib目录下的类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中。其本身属于JVM虚拟机的一部分,所以不能被Java代码中引用。 - 扩展类加载器(Extensions Class Loader)
用来加载Java的扩展类(可以自行添加扩展类),主要是将jre/lib/etc下的类库加载到内存中。 - 应用程序类加载器(Application Class Loader)
也叫系统加载类,是我们最常用的加载器,即环境变量内的CLASSPATH配置的路径。 - 自定义类加载器
由用户编写的继承ClassLoader类的加载器。应用场景通常是加密和网络方面。本次不讨论。
类加载顺序
1. 思想
前提是被不同的类加载器加载的类一定是不同的类。设计思想主要是避免同一个类被不同的加载器加载,加载之前先查看父类是否已经加载,没有加载在尝试加载,如果还不可以才自己加载。加载顺序如下图:
2. 有趣的实验
现在我们做一个有趣的实验来感受下类加载的过程。首先新建一个记事本,文件名及后缀名为MyTest9.java,编写下图代码。
public class MyTest9 {
public static void main(String[] args) {
System.out.println("程序员,很轻松的,就是头上有点凉。");
}
}
然后cmd打开控制台,cd进入到此文件的目录,输入如下命令,会发现多出一个MyTest9.class和MyTest9.jar文件。
javac -encoding UTF-8 MyTest9.java
jar cvf MyTest9.jar MyTest9.class
重点来了我们把这个jar包放到C:\Program Files\Java\jre1.8.0_172\lib\ext下,注意,我使用的是JDK8,默认安装是有一个独立的jre的。
接着修改MyTest9.java文件,如下:
public class MyTest9 {
public static void main(String[] args) {
System.out.println("程序员,一块钱四个,嘿嘿......");
}
}
最后,将使用javac -encoding UTF-8 MyTest9.java
把MyTest9.java重新编译一次,输入java MyTest
,输出结果如下:
为什么不会输出程序员,一块钱四个,嘿嘿......
这句呢,这大概就是双亲委托类加载机制。
启动器加载做了什么?
要查看启动类加载器做了什么就只能看源码了。本次查看的源码是OpenJDK8,请自行到官网下载。下载完源码解压后就使用反编译工具打开。这里使用的是Jd-gui。
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();//这里启动了扩展类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);//这里启动了应用程序类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
// Finally, install a security manager if requested
String s = System.getProperty("java.security.manager");
if (s != null) {
SecurityManager sm = null;
if ("".equals(s) || "default".equals(s)) {
sm = new java.lang.SecurityManager();
} else {
try {
sm = (SecurityManager)loader.loadClass(s).newInstance();
} catch (IllegalAccessException e) {
} catch (InstantiationException e) {
} catch (ClassNotFoundException e) {
} catch (ClassCastException e) {
}
}
if (sm != null) {
System.setSecurityManager(sm);
} else {
throw new InternalError(
"Could not create SecurityManager: " + s);
}
}
}
分别加载了什么类库?
如果想查看各个加载器到底加载了什么类库,可以使用以下代码查看。
package com.briup.ch25;
import java.util.Properties;
/**
* 显示各种类加载器加载的类库
*/
public class MyTest8 {
public static void main(String[] args) {
// 获得所有系统属性
Properties properties = System.getProperties();
// 查看启动类加载器加载了什么
String property = properties.getProperty("sun.boot.class.path");
String[] split = property.split(";");
for (String string : split) {
System.out.println(string);
}
System.out.println("-------------------伟大的分割线----------------------");
// 查看扩展类加载器加载了什么
String property1 = properties.getProperty("java.ext.dirs");
String[] split1 = property1.split(";");
for (String string : split1) {
System.out.println(string);
}
System.out.println("-------------------伟大的分割线----------------------");
// 查看应用程序类加载器加载了什么
String property2 = properties.getProperty("java.class.path");
String[] split2 = property2.split(";");
for (String string : split2) {
System.out.println(string);
}
}
}
结果如下
C:\Program Files\Java\jdk1.8.0_172\jre\lib\resources.jar
C:\Program Files\Java\jdk1.8.0_172\jre\lib\rt.jar
C:\Program Files\Java\jdk1.8.0_172\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.8.0_172\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.8.0_172\jre\lib\jce.jar
C:\Program Files\Java\jdk1.8.0_172\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.8.0_172\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.8.0_172\jre\classes
-------------------伟大的分割线----------------------
C:\Program Files\Java\jdk1.8.0_172\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
-------------------伟大的分割线----------------------
F:\briup\work\spring-tool-suite-4-4.3.1.RELEASE-e4.12.0-win32.win32.x86_64\workspace\jd1908_corejava_hdy\bin
F:\briup\work\spring-tool-suite-4-4.3.1.RELEASE-e4.12.0-win32.win32.x86_64\workspace\jd1908_corejava_hdy\lib\lombok.jar
F:\briup\work\spring-tool-suite-4-4.3.1.RELEASE-e4.12.0-win32.win32.x86_64\workspace\jd1908_corejava_hdy\lib\springmvc.jar
F:\briup\work\spring-tool-suite-4-4.3.1.RELEASE-e4.12.0-win32.win32.x86_64\workspace\jd1908_corejava_hdy\lib\jdk-misc-2.Final.jar
F:\briup\work\spring-tool-suite-4-4.3.1.RELEASE-e4.12.0-win32.win32.x86_64\workspace\jd1908_corejava_hdy\lib\juniversalchardet-1.0.3.jar
总结
写这个类加载还是花了很多时间的,因为我也是个初学者,只是把很多前人的经验复刻一下,但还是从中学到了点什么。
参考博客
https://www.jianshu.com/p/353c26c744df
https://www.cnblogs.com/gc65/p/10170853.html