一、双亲委派模型
咱们晓得类加载机制是将一个类从字节码文件转化为虚拟机能够间接应用类的过程,然而是谁来执行这个过程中的加载过程,它又是如何实现或者说保障了类加载的准确性和安全性呢?答案就是类加载器以及双亲委派机制。
双亲委派模型的工作机制是:当类加载器接管到类加载的申请时,它不会本人去尝试加载这个类,而是把这个申请委派给父加载器去实现,只有当父加载器反馈本人无奈实现这个加载申请时,子类加载器才会尝试本人去加载。
咱们能够从 JDK 源码中将它的工作机制一窥到底。
ClassLoader#loadClass(String,boolean)
这是在 JDK1.8 的 java.lang.ClassLoader 类中的源码,这个办法就是用于加载指定的类。
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) {long t0 = System.nanoTime();
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.
// 如果父类未实现加载,应用以后类加载器去加载该类
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;
}
}
通过以上代码得出结论:
- 当类加载器接管到类加载的申请时,首先查看该类是否曾经被以后类加载器加载;
- 若该类未被加载过,以后类加载器会将加载申请委托给父类加载器去实现;
- 若以后类加载器的父类加载器为 null,会委托启动类加载器实现加载;
- 若父类加载器无奈实现类的加载,以后类加载器才会去尝试加载该类。
类加载器分类
启动类加载器 Bootstrap ClassLoader
启动类加载器作为所有类加载器的鼻祖,是由 C ++ 实现的,不继承 java.lang.ClassLoader 类。它在虚拟机启动时会由虚拟机的一段 C ++ 代码进行加载,所以它没有父类加载器,在加载实现后,它会负责去加载扩大类加载器和利用类加载器。
启动类加载器用于加载 java 的外围类,位于 JAVA_HOME\lib 中,或者被 -Xbootclasspath 参数所指定的门路中,并且是虚拟机可能辨认的类库。
扩大类加载器 Extension ClassLoader
拓展类加载器继承于 java.lang.ClassLoader 类,它的父类加载器是启动类加载器,而启动类加载器在 java 中的显示就是 null。
引⾃ jdk1.8 ClassLoader#getParent() ⽅法的正文,这个⽅法是⽤于获取类加载器的⽗类加载器: Returns the parent class loader for delegation. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class loader’s parent is the bootstrap class loader
扩大类加载器负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 零碎变量指定门路中的类库。
留神:扩大类加载器仅反对加载被打包为 .jar 格局的字节码文件。
利用类 / 零碎类加载器 Application ClassLoader
JVM 通过双亲委派模型进行类的加载,当然咱们也能够通过继承 java.lang.ClassLoader 实现自定义的类加载器。
自定义类加载器 Custom ClassLoader
自定义类加载器继承于 java.lang.ClassLoader 类,它的父类加载器是利用类加载器。
这是用户自定义的类加载器,可加载指定门路的字节码文件。
自定义类加载器须要继承 java.lang.ClassLoader 类并重写 findClass 办法,用于实现自定义的加载类逻辑。
双亲委派模型的益处
基于双亲委派模型规定的这种带有优先级的层次性关系,虚拟机运行程序时就可能防止类的反复加载。
当父类类加载器曾经加载过类时,如果再有该类的加载申请传递到子类加载器,子类加载器执行 loadClass 办法,而后委托给父类加载器尝试加载该类,然而父类加载器执行 Class<?> c = findLoadedClass(name);查看该类是否曾经被加载过这一阶段就会查看到该类曾经被加载过,间接返回该类,而不会再次加载此类。
双亲委派模型可能防止外围类篡改。个别咱们形容的外围类是 rt.jar、tools.jar 这些由启动类加载器加载的类,这些类库在日常开发中被宽泛使用,如果被篡改,结果将不堪设想。
双亲委派模型的有余
因为历史起因(ClassLoader 类在 JDK1.0 时就曾经存在,⽽双亲委派模型是在 JDK1.2 之后才引⼊的),在未引⼊双亲委派模型时,⽤户⾃定义的类加载器须要继承 java.lang.ClassLoader 类并重写 loadClass() ⽅法,因为虚拟机在加载类时会调⽤ ClassLoader#loadClassInternal(String),⽽这个⽅法(源码如下)会调⽤⾃定义类加载重写的 loadClass() ⽅法。⽽在引⼊双亲委派模型后,ClassLoader#loadClass ⽅法理论就是双亲委派模型的实现,如果重写了此⽅法,相当于突破了双亲委派模型。为了让⽤户⾃定义的类加载器也听从双亲委派模型,JDK 新增了 findClass ⽅法,⽤于实现⾃定义的类加载逻辑。
private Class<?> loadClassInternal(String name) throws ClassNotFoundException{
// For backward compatibility, explicitly lock on 'this' when
// the current class loader is not parallel capable.
if (parallelLockMap == null) {synchronized (this) {return loadClass(name);
}
} else {return loadClass(name);
}
}
因为双亲委派模型规定的层次性关系,导致⼦类类加载器加载的类能拜访⽗类类加载器加载的类,⽽⽗类类加载器加载的类⽆法拜访⼦类类加载器加载的类。为了让下层类加载器加载的类可能拜访上层类加载器加载的类,或者说让⽗类类加载器委托⼦类类加载器实现加载申请,JDK 引⼊了线程高低⽂类加载器,藉由它来突破双亲委派模型的屏障。
当⽤户须要程序的动态性,⽐如代码热替换、模块热部署等时,双亲委派模型就不再适⽤,类加载器会倒退为更为简单的⽹状构造。
总结
最初
在文章的最初作者为大家整顿了很多材料!包含 java 外围知识点 + 全套架构师学习材料和视频 + 一线大厂面试宝典 + 面试简历模板 + 阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题 +Spring 源码合集 +Java 架构实战电子书等等!欢送关注公众号:前程有光,支付!