ClassLoader是用来动态的加载class文件到虚拟机中,并转换成java.lang.class类的一个实例,每一个这样的实例用来表示一个java类,咱们能够根据Class的实例获得该类的信息,并经过实例的newInstance()方法建立出该类的一个对象,除此以外,ClassLoader还负责加载Java应用所需的资源,如图像文件和配置文件等。html
ClassLoader类是一个抽象类。若是给定类的二进制名称,那么类加载器会试图查找或生成构成类定义的数据。通常策略是将名称转换为某个文件名,而后从文件系统读取该名称的“类文件”。ClassLoader类使用委托模型来搜索类和资源。每一个 ClassLoader实例都有一个相关的父类加载器。须要查找类或资源时,ClassLoader实例会在试图亲自查找类或资源以前,将搜索类或资源的任务委托给其父类加载器。 java
注意:程序在启动的时候,并不会一次性加载程序所要用的全部class文件,而是根据程序的须要,经过Java的类加载机制来动态加载某个class文件到内存中。数组
其体系结构图以下:服务器
若是要实现本身的类加载器,不论是实现抽象列ClassLoader,仍是继承URLClassLoader类,它的父加载器都是AppClassLoader,由于无论调用哪一个父类加载器,建立的对象都必须最终调用getSystemClassLoader()做为父加载器,getSystemClassLoader()方法获取到的正是AppClassLoader。网络
注意:Bootstrap classLoader并不属于JVM的等级层次,它不遵照ClassLoader的加载规则,Bootstrap classLoader并无子类。数据结构
JVM提供的类加载器,只能加载指定目录的jar和class,若是咱们想加载其余位置的类或jar时,例如加载网络上的一个class文件,默认的ClassLoader就不能知足咱们的需求了,因此须要定义本身的类加载器。函数
咱们实现一个ClassLoader,并指定这个ClassLoader的加载路径。有两种方式:this
方式一:继承ClassLoader,重写父类的findClass()方法,代码以下:spa
1 import java.io.ByteArrayOutputStream; 2 import java.io.File; 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 public class PathClassLoader extends ClassLoader 6 { 7 public static final String drive = "d:/"; 8 public static final String fileType = ".class"; 9 10 public static void main(String[] args) throws Exception 11 { 12 PathClassLoader loader = new PathClassLoader(); 13 Class<?> objClass = loader.loadClass("HelloWorld", true); 14 Object obj = objClass.newInstance(); 15 System.out.println(objClass.getName()); 16 System.out.println(objClass.getClassLoader()); 17 System.out.println(obj.getClass().toString()); 18 } 19 20 public Class<?> findClass(String name) 21 { 22 byte[] data = loadClassData(name); 23 return defineClass(name, data, 0, data.length);// 将一个 byte 数组转换为 Class// 类的实例 24 } 25 public byte[] loadClassData(String name) 26 { 27 FileInputStream fis = null; 28 byte[] data = null; 29 try 30 { 31 fis = new FileInputStream(new File(drive + name + fileType)); 32 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 33 int ch = 0; 34 while ((ch = fis.read()) != -1) 35 { 36 baos.write(ch); 37 } 38 data = baos.toByteArray(); 39 } catch (IOException e) 40 { 41 e.printStackTrace(); 42 } 43 return data; 44 } 45 }
在第13行,咱们调用了父类的loadClass()方法,该方法使用指定的二进制名称来加载类,下面是loadClass方法的源代码:code
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 第一步先检查这个类是否已经被加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //parent为父加载器 if (parent != null) { //将搜索类或资源的任务委托给其父类加载器 c = parent.loadClass(name, false); } else { //检查该class是否被BootstrapClassLoader加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { //若是上述两步均没有找到加载的class,则调用findClass()方法 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; } }
这个方法首先检查指定class是否已经被加载,若是已被加载过,则调用resolveClass()方法连接指定的类,若是还未加载,则先将搜索类或资源的任务委托给其父类加载器,检查该class是否被BootstrapClassLoader加载,若是上述两步均没有找到加载的class,则调用findClass()方法,在咱们自定义的加载器中,咱们重写了findClass方法,去咱们指定的路径下加载class文件。
另外,咱们自定义的类加载器没有指定父加载器,在JVM规范中不指定父类加载器的状况下,默认采用系统类加载器即AppClassLoader做为其父加载器,因此在使用该自定义类加载器时,须要加载的类不能在类路径中,不然的话根据双亲委派模型的原则,待加载的类会由系统类加载器加载。若是必定想要把自定义加载器须要加载的类放在类路径中, 就要把自定义类加载器的父加载器设置为null。
方式二:继承URLClassLoader类,而后设置自定义路径的URL来加载URL下的类。
咱们将指定的目录转换为URL路径,而后重写findClass方法。
所谓热部署,就是在应用正在运行的时候升级软件,不须要从新启用应用。
对于Java应用程序来讲,热部署就是运行时更新Java类文件。在基于Java的应用服务器实现热部署的过程当中,类装入器扮演着重要的角色。大多数基于Java的应用服务器,包括EJB服务器和Servlet容器,都支持热部署。
类装入器不能从新装入一个已经装入的类,但只要使用一个新的类装入器实例,就能够将类再次装入一个正在运行的应用程序。
前面的分析,咱们已经知道,JVM在加载类以前会检查请求的类是否已经被加载过来,也就是要调用findLoadedClass方法查看是否可以返回类实例。若是类已经加载过来,再调用loadClass会致使类冲突。
可是,JVM判断一个类是不是同一个类有两个条件:一是看这个类的完整类名是否同样(包括包名),二是看加载这个类的ClassLoader加载器是不是同一个(既是是同一个ClassLoader类的两个实例,加载同一个类也会不同)。
因此,要实现类的热部署能够建立不一样的ClassLoader的实例对象,而后经过这个不一样的实例对象来加载同名的类。