上文说了类加载过程的5个阶段,着重介绍了各个阶段作的工做。在本文中,咱们对执行加载阶段的主体进行探讨,学习类加载器的模型和逻辑,以及咱们该如何自定义一个类加载器。java
前面说过加载阶段是一个可让设计人员高度自控的模块,由于类文件的源头能够是多种多样的,代码生成、反射生成或从网络中生成等。所以类加载器做为对这些文件的处理就显得尤其重要。数组
但类加载器的功能不只如此,其还有一个重要的功能就是和一个类的全限定名惟一肯定一个类。通俗来讲,要说两个类是相同的,不只其全限定名要同样,其对应的类加载器也必须相同,才能说明两个类是相等的。网络
正由于类加载器的功能角色如此重要,所以虚拟机对其的实现规范也十分重视。在Java虚拟机中,对其的实现模型是双亲委派模型。jvm
双亲委派模型的主要执行过程示意图如上所示,其分为启动类加载器(Bootstrap Class-loader),拓展类加载器(Extension Class-loader),应用程序类加载(Application Class-loader)。ide
其中启动类加载器主要负责加载 JRE 的核心类库,如 JRE 目录下的 rt.jar。但其实根据《深刻分析 Java Web 技术内幕》上所说,启动类加载器并不严格符合双亲委派模型,由于Bootstrap Class-loader 并不属于 JVM 的类等级层次。Bootstrap Class-loader 是没有子类的,Extension Class-loader 也是没有父类的。不过在这里咱们并不深究,只要知道有这一点就能够了。源码分析
Extension Class-loader 主要负责加载 JRE 拓展目录 ext 下的类。布局
Application Class-loader 主要负责用户类路径(Class-path)下的类,这个类加载器是使用的最多的,由于大大多数状况下,通常开发者并无实现自定义的类加载器,那么 JVM 就会使用这个来加载类大部分类。学习
上图就是双亲委派模型的执行过程,当类开始加载的时候,先检查是否已经被加载过,若是没有被加载过,则调用父类的加载方法,若是父类加载失败,抛出异常,则调用自身的 findClass() 方法进行加载。this
JDK 中加载过程的源码分析:url
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 { // 无父亲就调用 bootstarp 加载器来加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 父加载器和 bootstarp 加载器都没有找到指定类,调用当前类的 findClass() 来完成类加载 // 所以,自定义类加载器,就是重写 findClass() 方法 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; } }
从源码中,咱们能够看到其实符合规范要求的 双亲委派模型 的。而当咱们要自定义一个类加载器的时候就是经过重写 findClass() 来实现的。
/** * 1. 自定义类加载器经过集成ClassLoader来实现,主要经过重写findClass方法 * 2. findClass方法首先经过自定义的loadByte()方法将Class文件转换成byte[]字节流 * 3. 而后经过defineClass()方法将其转换为Class对象 */ public class SelfClassLoader extends ClassLoader { private String classPath; public SelfClassLoader(String classPath) { this.classPath = classPath; } /** * 经过 difineClass,将一个字节数组转换为Class 对象 * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * 根据路径将指定的文件读取为byte 流 * @param name * @return * @throws IOException */ private byte[] loadByte(String name) throws IOException { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } }
另外一个种实现自定义类加载器的方法:
/** * 1. 加载指定packageName下的类 * 2. 用自定义类加载器进行加载,若是加载失败,再交给父加载器进行加载 */ public class UrlSelfClassloader extends URLClassLoader { private String packageName = ""; public UrlSelfClassloader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { Class<?> aClass = findLoadedClass(name); if (Objects.nonNull(aClass)){ return aClass; } if (!packageName.startsWith(name)){ return super.loadClass(name); }else { return findClass(name); } } }
如何使用自定义的类加载器
public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader(""); Class clazz = classLoader.loadClass(""); Object obj = clazz.newInstance(); Method helloMethod = clazz.getDeclaredMethod("hello", null); helloMethod.invoke(obj, null); }
在本文中,咱们讲解了类加载器的实现模型,分析了在 JDK 中类加载器的源码实现,并根据源码中的代码实现,自定义了一个类加载器的实现。
此外相信通过五和六两篇文章的学习,你们应该对如何将类加载入虚拟机中有了系统的理解。
后面的文章中,咱们就要进入 JVM 的内部了,从下篇文章开始,咱们就开始逐步讲解 JVM 的内存布局,了解 JVM 中的各个逻辑上划分的存储结构以及其做用,欢迎各位读者浏览。
文章在公众号「iceWang」第一手更新,有兴趣的朋友能够关注公众号,第一时间看到笔者分享的各项知识点,谢谢!笔芯!
本系列文章主要借鉴《深刻分析 Java Web 技术内幕》和《深刻理解 Java 虚拟机 - JVM 高级特性与最佳实践》。