关注“Java后端技术全栈”java
回复“000”获取大量电子书面试
类加载器是不少人认为很硬的骨头。其实也没那么可怕,请听老田慢慢道来。编程
在装载(Load)阶段,经过类的全限定名获取其定义的二进制字节流,须要借助类装载器完成,顾名思义,就是用来装载Class文件的。
上面咱们自定义一个String出了问题,问题在于JVM
不知道咱们想用哪一个类,因而JVM
就定义了个规范。后端
把这种类装载器分红几类。缓存
负责加载$JAVA_HOME中 jre/lib/rt.jar
里全部的class或Xbootclassoath
选项指定的jar包。由C++实现,不是ClassLoader
子类。tomcat
负责加载Java
平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar
或 -Djava.ext.dirs
指定目录下的jar包。网络
负责加载classpath
中指定的jar包及 Djava.class.path
所指定目录下的类和jar包。架构
经过java.lang.ClassLoader
的子类自定义加载class,属于应用程序根据自身须要自定义的ClassLoader
,如tomcat
、jboss
都会根据j2ee
规范自行实现ClassLoader
。框架
检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader
到BootStrap ClassLoader
逐层检查,只要某个Classloader
已加载,就视为已加载此类,保证此类只全部ClassLoader
加载一次。ide
加载的顺序:加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
java.lang.ClassLoader
中很重要的三个方法:
loadClass
方法
findClass
方法
defineClass
方法
1 public Class<?> loadClass(String name) throws ClassNotFoundException { 2 return loadClass(name, false); 3 } 4 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ 5 //使用了同步锁,保证不出现重复加载 6 synchronized (getClassLoadingLock(name)) { 7 // 首先检查本身是否已经加载过 8 Class<?> c = findLoadedClass(name); 9 //没找到 10 if (c == null) { 11 long t0 = System.nanoTime(); 12 try { 13 //有父类 14 if (parent != null) { 15 //让父类去加载 16 c = parent.loadClass(name, false); 17 } else { 18 //若是没有父类,则委托给启动加载器去加载 19 c = findBootstrapClassOrNull(name); 20 } 21 } catch (ClassNotFoundException e) { 22 // ClassNotFoundException thrown if class not found 23 // from the non-null parent class loader 24 } 25 26 if (c == null) { 27 // If still not found, then invoke findClass in order 28 // to find the class. 29 long t1 = System.nanoTime(); 30 // 若是都没有找到,则经过自定义实现的findClass去查找并加载 31 c = findClass(name); 32 33 // this is the defining class loader; record the stats 34 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 35 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 36 sun.misc.PerfCounter.getFindClasses().increment(); 37 } 38 } 39 //是否须要在加载时进行解析 40 if (resolve) { 41 resolveClass(c); 42 } 43 return c; 44 } 45 }
正如loadClass
方法所展现的,当类加载请求到来时,先从缓存中查找该类对象,若是存在直接返回,若是不存在则交给该类加载去的父加载器去加载,假若没有父加载则交给顶级启动类加载器去加载,最后假若仍没有找到,则使用findClass()
方法去加载(关于findClass()
稍后会进一步介绍)。
从loadClass
实现也能够知道,若是不想从新定义加载类的规则,也没有复杂的逻辑,只想在运行时加载本身指定的类,那么咱们能够直接使用this.getClass().getClassLoder.loadClass("className")
,这样就能够直接调用ClassLoader
的loadClass
方法获取到class对象。
1 protected Class<?> findClass(String name) throws ClassNotFoundException { 2 throw new ClassNotFoundException(name); 3 }
在JDK1.2
以前,在自定义类加载时,总会去继承ClassLoade
r类并重写loadClass
方法,从而实现自定义的类加载类,可是在JDK1.2
以后已再也不建议用户去覆盖loadClass
()方法,而是建议把自定义的类加载逻辑写在findClass()
方法中。
从前面的分析可知,findClass()
方法是在loadClass
()方法中被调用的,当loadClass
()方法中父加载器加载失败后,则会调用本身的findClass()
方法来完成类加载,这样就能够保证自定义的类加载器也符合双亲委托模式。
须要注意的是,ClassLoader
类中并无实现findClass()
方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException
异常。同时应该知道的是,findClass
方法一般是和defineClass
方法一块儿使用的。
1 protected final Class<?> defineClass(String name, byte[] b, int off, int len, 2 ProtectionDomain protectionDomain) throws ClassFormatError{ 3 protectionDomain = preDefineClass(name, protectionDomain); 4 String source = defineClassSourceLocation(protectionDomain); 5 Class<?> c = defineClass1(name, b, off, len, protectionDomain, source); 6 postDefineClass(c, protectionDomain); 7 return c; 8 }
defineClass()
方法是用来将byte字节流解析成JVM
可以识别的Class对象。
经过这个方法不只可以经过class文件实例化class对象,也能够经过其余方式实例化class对象,如经过网络接收一个类的字节码,而后转换为byte字节流建立对应的Class对象 。
用户根据需求本身定义的。须要继承自ClassLoader
,重写方法findClass()
。
若是想要编写本身的类加载器,只须要两步:
ClassLoader
类findClass(String className)
方法**ClassLoader**
超类的loadClass
方法用于将类的加载操做委托给其父类加载器去进行,只有当该类还没有加载而且父类加载器也没法加载该类时,才调用findClass
方法。
若是要实现该方法,必须作到如下几点:
1.为来自本地文件系统或者其余来源的类加载其字节码。
2.调用ClassLoader
超类的defineClass
方法,向虚拟机提供字节码。
这个在面试中也是频率至关高。
若是一个类加载器在接到加载类的请求时,先查找是否已经加载过,若是没有被加载过,它首先不会本身尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归。
若是父类加载器能够完成类加载任务,就成功返回;只有父类加载器没法完成此加载任务时,才本身去加载。
Java类随着加载它的类加载器一块儿,具有了一种带有优先级的层次关系。
好比,Java中的Object类,它存放在rt.jar之中,不管哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,所以Object在各类类加载环境中都是同一个类。
若是不采用双亲委派模型,那么由各个类加载器本身取加载的话,那么系统中会存在多种不一样的Object类。
tomcat 经过 war 包进行应用的发布,它实际上是违反了双亲委派机制原则的。简单看一下 tomcat 类加载器的层次结构。
对于一些须要加载的非基础类,会由一个叫做 WebAppClassLoader 的类加载器优先加载。等它加载不到的时候,再交给上层的 ClassLoader 进行加载。这个加载器用来隔毫不同应用的 .class 文件,好比你的两个应用,可能会依赖同一个第三方的不一样版本,它们是相互没有影响的。
如何在同一个 JVM 里,运行着不兼容的两个版本,固然是须要自定义加载器才能完成的事。
那么 tomcat 是怎么打破双亲委派机制的呢?
能够看图中的 WebAppClassLoader,它加载本身目录下的 .class 文件,并不会传递给父类的加载器。可是,它却可使用 SharedClassLoader 所加载的类,实现了共享和分离的功能。
可是你本身写一个 ArrayList,放在应用目录里,tomcat 依然不会加载。它只是自定义的加载器顺序不一样,但对于顶层来讲,仍是同样的。
OSGi 曾经很是流行,Eclipse 就使用 OSGi 做为插件系统的基础。
OSGi 是服务平台的规范,旨在用于须要长运行时间、动态更新和对运行环境破坏最小的系统。
OSGi 规范定义了不少关于包生命周期,以及基础架构和绑定包的交互方式。这些规则,经过使用特殊 Java 类加载器来强制执行,比较霸道。
好比,在通常 Java 应用程序中,classpath 中的全部类都对全部其余类可见,这是毋庸置疑的。可是,OSGi 类加载器基于 OSGi 规范和每一个绑定包的 manifest.mf 文件中指定的选项,来限制这些类的交互,这就让编程风格变得很是的怪异。但咱们不难想象,这种与直觉相违背的加载方式,确定是由专用的类加载器来实现的。
随着 jigsaw 的发展(旨在为 Java SE 平台设计、实现一个标准的模块系统),我我的认为,如今的 OSGi,意义已经不是很大了。
OSGi 是一个庞大的话题,你只须要知道,有这么一个复杂的东西,实现了模块化,每一个模块能够独立安装、启动、中止、卸载,就能够了。
Java 中有一个 SPI 机制,全称是 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩展的 API,它能够用来启用框架扩展和替换组件。
后面会再专门针对这个写一篇文章,这里就不细说了。
推荐阅读:
关注公众号“Java后端技术全栈”
免费获取500G最新学习资料