每个类加载器都有自己的一个加载路径,当某个类加载器需要加载某个.class文件时,它不是立刻从自己的加载路径中去找这个class文件,而是委派给它的父类加载器去加载。它的父类加载器又委派给父类的父类加载器去加载。
Class Loader的作用是,将静态的class文件加载成Class对象,加入jvm
public static void bootstrapClassLoaderTest(){
System.out.println(Main.class.getClassLoader().getParent().getParent());
//打印加载路径
System.out.println(System.getProperty("sun.boot.class.path"));
}
输出结果如下:
null D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;D:\jdk1.8\jre\lib\sunrsasign.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\classes
null,代表bootstrapClassLoader,它是用c++写的。在java找不到它的引用,所以这里为空
Bootstrap ClassLoader是根类加载器,是最顶层的加载器,没有父类加载器,主要负责虚拟机核心类库的加载。其加载路径为sun.boot.class.path,大致是$JAVA_HOME/jre/lib下面的rt.jar、charsets.jar和classes等。启动jvm,可以通过指定-Xbootclasspath,来改变Bootstrap ClassLoader的加载路径:java -Xbootclasspath:c:/path1/jar1.jar;c:/path2/jar2.jar Test
public static void extClassLoaderTest(){
System.out.println(Main.class.getClassLoader().getParent());
System.out.println(System.getProperty("java.ext.dirs"));
}
输出结果如下:
sun.misc.Launcher$ExtClassLoader@1b6d3586 D:\jdk1.8\jre\lib\ext;C:\windows\Sun\Java\lib\ext
Ext ClassLoader为拓展类加载器,它的父类加载器是Bootstrap ClassLoader,其加载路径为java.ext.dirs,也就是$JAVA_HOME/jre/lib/ext目录
public static void appClassLoaderTest(){
System.out.println(Main.class.getClassLoader());
System.out.println(System.getProperty("java.class.path"));
}
输出结果如下:
sun.misc.Launcher$AppClassLoader@18b4aac2 D:\jdk1.8\jre\lib\charsets.jar;D:\jdk1.8\jre\lib\deploy.jar;D:\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8\jre\lib\ext\cldrdata.jar;D:\jdk1.8\jre\lib\ext\dnsns.jar;D:\jdk1.8\jre\lib\ext\jaccess.jar;D:\jdk1.8\jre\lib\ext\jfxrt.jar;D:\jdk1.8\jre\lib\ext\localedata.jar;D:\jdk1.8\jre\lib\ext\nashorn.jar;D:\jdk1.8\jre\lib\ext\sunec.jar;D:\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8\jre\lib\ext\zipfs.jar;D:\jdk1.8\jre\lib\javaws.jar;D:\jdk1.8\jre\lib\jce.jar;D:\jdk1.8\jre\lib\jfr.jar;D:\jdk1.8\jre\lib\jfxswt.jar;D:\jdk1.8\jre\lib\jsse.jar;D:\jdk1.8\jre\lib\management-agent.jar;D:\jdk1.8\jre\lib\plugin.jar;D:\jdk1.8\jre\lib\resources.jar;D:\jdk1.8\jre\lib\rt.jar;E:\workspace\apache-tomcat-8.0.11-src\test\out\production\test;D:\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar
App ClassLoader为应用程序类加载器,也叫系统类加载器,负责加载当前应用类路径下的所有类。其加载路径为java.class.path,也就是$CLASSPATH。可是我自己电脑操作系统的环境变量CLASSPATH为.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;%ANT_HOME%\lib。(JAVA_HOME为D:\JDK1.8)这与上面的输出结果不一致吧,为什么呢?因为IntelliJ IDEA 在工程中重新配置了依赖包,相当于将系统属性java.class.path的值,重新配置了一遍
你怎么知道双亲委派机制就是上面描述的那样?有证据吗?证据就在ClassLoader#loadClass(java.lang.String)
//ClassLoader#loadClass(java.lang.String)
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
//ClassLoader#loadClass(java.lang.String, boolean)
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//从当前类加载器的已加载类缓存中,根据类的全路径查询类是否已经加载过
Class> c = findLoadedClass(name);
//如果不在当前类加载器的已加载类缓存中
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果有父类的加载器,就委派给父类加载器去加载
c = parent.loadClass(name, false);
} else {
//如果父类加载器为空,则说明递归到BootstrapClassLoader。BootstrapClassLoader是用c++写的,在java上找不到它的引用,所以为空。
//委派给BootstrapClassLoader去加载
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.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
所有的自定义类加载器,都是ClassLoader的直接子类或间接子类。java.lang.ClassLoader是一个抽象类,它里面没有抽象方法,但是有findClass方法,子类必须要实现该方法,不然会抛出ClassNotFoundException
//ClassLoader#findClass
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
public class CustomizeClassLoader extends ClassLoader {
private final static Path DEFAULT_CLASS_DIR = Paths.get("E:", "classloader1");
private final Path classDir;
public CustomizeClassLoader() {
super();
this.classDir = DEFAULT_CLASS_DIR;
}
/**
* 指定加载路径
*
* @param classDir
*/
public CustomizeClassLoader(String classDir) {
super();
this.classDir = Paths.get(classDir);
}
/**
* 指定加载路径的同时,指定父类加载器
*
* @param parent
* @param classDir
*/
public CustomizeClassLoader(ClassLoader parent, String classDir) {
super(parent);
this.classDir = Paths.get(classDir);
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
//读取class的二进制数据
byte[] classBytes = this.readClassBytes(name);
//如果数据为null,或者没有读到任何信息,则抛出ClassNotFoundException异常
if(null == classBytes || classBytes.length == 0){
throw new ClassNotFoundException("Can not load the class " + name);
}
//调用defineClass方法定义class
return this.defineClass(name, classBytes, 0, classBytes.length);
}
private byte[] readClassBytes(String name) throws ClassNotFoundException {
//将包名分隔符转换为文件路径分隔符
String classpath = name.replace(".", "/");
Path classFullPath = classDir.resolve(Paths.get(classpath + ".class"));
if(!classFullPath.toFile().exists()){
throw new ClassNotFoundException("The class " + name + " not found.");
}
try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
Files.copy(classFullPath, baos);
return baos.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("load the class " + name + " occur error.", e);
}
}
}
CustomizeClassLoader可以加载E:\classloader1目录下的.class文件,如果刚巧这个项目的CLASSPATH就是E:\classloader1,那么CustomizeClassLoader还能加载到.class文件吗?不能,由双亲委派机制可知,AppClassLoader会先加载到。那么有没有什么办法,让CustomizeClassLoader加载到.class文件呢?有两种办法可以做到:
1、绕过AppClassLoader,将拓展类加载器或跟类加载器作为父类
ClassLoader extClassLoader = Main.class.getClassLoader().getParent();
CustomizeClassLoader customizeClasLoader = new CustomizeClassLoader(extClassLoader, "E:\\classloader1");
2、破坏双亲委派机制,重写loadClass方法,先进行自定义类加载器的加载
/**
* @author: create by Rhine
* @date:2020/7/28 20:57
* @description:
*/
public class BrokerDelegateClassLoader extends ClassLoader {
...
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//从当前类加载器的已加载类缓存中,根据类的全路径查询类是否已经加载过
Class> c = findLoadedClass(name);
//如果不在当前类加载器的已加载类缓存中
if (c == null) {
//如果类的全路径以java或javax开头,那么直接委派给系统类加载器加载
if (name.startsWith("java.") || name.startsWith("javax")) {
try {
c = getSystemClassLoader().loadClass(name);
} catch (Exception e) {
//ignore
}
} else {
//自定义类加载器,先进行加载
try {
c = findClass(name);
} catch (Exception e) {
//ignore
}
//如果自定义类加载器,没有加载到
if (c == null) {
if (getParent() != null) {
//如果有父加载器,那么委派给父加载器去加载
c = getParent().loadClass(name);
} else {
//如果没有父加载器,那么委派给系统类加载器去加载
c = getSystemClassLoader().loadClass(name);
}
}
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
throw new ClassNotFoundException("The class " + name + " not found.");
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
...
}
我们知道BootstrapClassLoader、ExtClassLoader、AppClassLoader之间并不存在继承关系,所谓的父类加载器,只是个parent属性而已。那么这个parent属性,是在哪里设置进去的呢?JVM在启动时,会执行sun.misc.Launcher 类。在该类中,创建一个Extention ClassLoader 扩展类加载器,和一个应用类加载器AppClassLoader
//sun.misc.Launcher#Launcher
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//创建ExtClassLoader,其parent属性为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//创建AppClassLoader,其parent属性为ExtClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
...
}
防止重复加载同一个class。源码里面的Class> c = findLoadedClass(name)就能证明
防止核心class不被篡改,比如说如果你自己写一个java.lang.String类,那么由于双亲委派机制,加载到还是rt.jar中的java.lang.String类
《java高并发编程详解 多线程与架构设计》第10章JVM类加载器
https://www.cnblogs.com/crazymakercircle/p/9824111.html
https://www.jianshu.com/p/1e4011617650