赞
踩
类加载器的
双亲委派模型
在 JDK 1.2 时期被引入,并被广泛应用于此后几乎所有的 Java 程序中,但它并不是一个具有强制性的约束力的模型,而是 Java 设计者们推荐给开发者的一种加载器实现的最佳实践。
Java 虚拟机设计团队有意把类加载阶段中的 “ 通过一个类的全限定名来获取描述该类的二进制字节流 ” 这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为 “ 类加载器 ”(Class Loader)。
判断两个类的是否 “ 相等 ”,是由类和加载类的类加载器共同决定的。
这里包括 Class 对象的 equal(),isAssignableFrom(),isInstance()和 instanceof 关键字的判断。
从虚拟机的角度,只有两种类加载器,一种启动类加载器(Bootstrap ClassLoader),使用 C++实现,是虚拟机的一部分,另一种是继承自抽象类 java.lang.ClassLoader
的启动器。
但是在虚拟机实际中,提供了三种类加载器:
<JAVA_HOME>\lib
目录下,或者被-Xbootclasspath
参数指定的路径中存放的,Java虚拟机能识别的类名(如rt.jar、tools.jar
等),的类。(加载请求委派给引导类加载器时,使用null的。)<JAVA_HOME>\lib\ext
目录下,或者被java.ext.dirs
系统变量所指定的路径中的所有类。sun.misc.Launcher.ExtClassLoader
java.lang.ClassLoader#getSystemClassLoader
)。一般情况下系统默认的类加载器,也称 “ 系统类加载器 ”。sun.misc.Launcher.AppClassLoader
在虚拟机内部,加载器之间存在一定的关系如下:
而这种层级关系,是JDK 1.2以来,虚拟机设计者为开发者提供的类加载器最佳实践模型。这种类似父子关系的层级模型称为 “ 双亲委派模型 (Parents Delegation Model)”。这种父子关系并不是依赖于继承,而是使用的组合。
双亲委派模型的工作过程如下:
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; } }
虚拟机中三大类加载器建立双亲委派模型的过程源码:
sun.misc.Launcher
public Launcher() { Launcher.ExtClassLoader var1; try { //初始化【扩展类加载器】,没有入参,使用的是【启动类加载器】 var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { // 使用【扩展类加载器】初始化【应用程序类加载器】 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } // 使用【应用程序类加载器】初始化【线程上下文类加载器】 Thread.currentThread().setContextClassLoader(this.loader); //这是部分代码 }
扩展类加载器创建过程:
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { final File[] var0 = getExtDirs(); try { return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { public Launcher.ExtClassLoader run() throws IOException { int var1 = var0.length; for(int var2 = 0; var2 < var1; ++var2) { MetaIndex.registerDirectory(var0[var2]); } return new Launcher.ExtClassLoader(var0); } }); } catch (PrivilegedActionException var2) { throw (IOException)var2.getException(); } } public ExtClassLoader(File[] var1) throws IOException { // classLoader 为null 就是默认使用启动类加载器 super(getExtURLs(var1), (ClassLoader)null, Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); }
应用程序类加载:
// 这里创建时,传入的是扩展类加载器 public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); return new Launcher.AppClassLoader(var1x, var0); } }); } AppClassLoader(URL[] var1, ClassLoader var2) { //使用扩展类加载器作为父加载器创建 super(var1, var2, Launcher.factory); this.ucp.initLookupCache(this); }
采用双亲委派模式的好处是:
避免类的重复加载
。java核心api中定义类型不会被随意替换
。假设通过网络传递一个名为java.lang.Integer
的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心 Java API 发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer
,而直接返回已加载过的 Integer.class
,这样便可以防止核心 API 库被随意篡改。可能你会想,如果我们在 classpath 路径下自定义一个名为java.lang.SingleInterge
类(该类是胡编的)呢?该类并不存在java.lang
中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang
是核心 API 包,需要访问权限,强制加载将会报出如下异常
java.lang.SecurityException: Prohibited package name: java.lang
通过继承java.lang.ClassLoader
抽象类来实现。主要介绍一下核心方法
loadClass(java.lang.String)
该方法加载指定名称(包括包名)的二进制类型。JDK1.2 以后不要建议重写。负责构建双亲委派模型。
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } 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; } }
protected Class<?> findClass(String name)
但是在JDK1.2之后已不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面的分析可知,findClass()方法是在loadClass()方法中被调用的,当loadClass()方法中父加载器加载失败后,则会调用自己的findClass()方法来完成类加载,这样就可以保证自定义的类加载器也符合双亲委托模式。
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象(ClassLoader中已实现该方法逻辑),通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象,defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象。
举个例子
package com.hyl.learnerJVM.load; import java.io.IOException; import java.io.InputStream; /** * 类加载器与 instanceof 关键词演示 * * @author hyl * @version v1.0: ClassLoaderTest.java, v 0.1 2020/8/13 13:30 $ */ public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".")+1)+".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null){ return super.loadClass(name); } byte[] b =new byte[is.available()]; is.read(b); return defineClass(name,b,0,b.length); }catch (IOException e){ throw new ClassNotFoundException(name); } } }; Object obj = myLoader.loadClass("com.hyl.learnerJVM.load.ClassLoaderTest").newInstance(); System.out.println( obj.getClass()); System.out.println( obj instanceof com.hyl.learnerJVM.load.ClassLoaderTest); ClassLoader myLoader2 = ClassLoader.getSystemClassLoader(); Object obj2 = myLoader2.loadClass("com.hyl.learnerJVM.load.ClassLoaderTest").newInstance(); System.out.println( obj2.getClass()); System.out.println( obj2 instanceof com.hyl.learnerJVM.load.ClassLoaderTest); Object obj3 =ClassLoaderTest.class.getClassLoader().loadClass("com.hyl.learnerJVM.load.ClassLoaderTest").newInstance(); System.out.println( obj3.getClass()); System.out.println( obj3 instanceof com.hyl.learnerJVM.load.ClassLoaderTest); } }
Java世界里破坏双亲委派模型的三次情况:
java.lang.ClassLoader
已经被引用,但是主要是通过重写 loadClass()
方法,这里还没有引入双亲委派模型。JDK1.2 以后 双亲委派模型 的逻辑写在了 loadClass()
方法内,在自定义加载类时,建议使用findClass()
方法。
为了能加载其他厂商实现并部署在应用程序的 ClassPath 下的 SPI。这些 SPI 是由启动类加载器加载,但是启动类加载器无法加载其他厂商代码,所以引入了线程上下文类加载器(Thread Context ClassLoader)。使用线程上下文加载器去加载 SPI 服务代码,是一种父类加载器去请求子类加载器完成类加载的行为,就违背了双亲委派模型的一般性原则,但是也无可奈何。例如:JDNI、JDBC、JCE、JAXB和JBI都是使用这种方式。
在 JDK 6时,JDK 提供了
java.util.ServiceLoader
类,以 META-INF/services 中的配置信息,辅以责任链模式,这才给 SPI 的加载提供了一种相对合理的解决方案。
代码热替换(Hot Sweep)、模块热部署(Hot Deployment)等。
OSGI
OSGI 实现模块化热部署的关键是它自定义的类加载器机制的实现,每一个程序模块( OSGI 中称为 Bundle)都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换。
OSGI 环境下,类加载器不再双亲委派模型推荐的树状结构,而是进一步发展为复杂的网状结构,当收到类的加载请求时,OSGI 将按照下面的顺序经行类搜索:
java.*
开头的类,委派给父类加载器加载。Import
列表中的类,委派给 Export
这个类的 Bundle
的类加载器加载。Bundle
的 ClassPath
,使用自己的类加载器加载。Fragment Bundle
中,如果在,则委派给 Fragment Bundle
的类加载器加载。Dynamic Import
列表的 Bundle
,委派给对应的 Bundle
的类加载器加载。1 和 2 还是遵从双亲委派模型,后面就没有了。
JDK9 ,开始了Java模块化系统(Java Platform Module System,JPMS)算是
第四次破坏双亲委派模型
,在加载时要先判断能够归属到某一个系统模块中,如果存在这种归属关系,就优先委派给负责那个模块的加载器完成加载。
破坏双亲委派模型不一定是贬义词,只要有明确的目的和充分的理由,突破旧有原则无疑也是一种创新。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。