ClassLoader顾名思义就是类加载器,负责将Class字节码文件加载到JVM中。
ClassLoader加载一个class文件到JVM时需要经过如下步骤:
Ø 第一阶段是找到.class文件并把这个文件包含的字节码加载到内存中;
Ø 第二阶段又可以分为三个步骤,分别是字节码验证、Class类数据结构分析以及相应的内存分配和最后的符号表的链接;
Ø 第三阶段是类中静态属性和初始化赋值,以及静态块的执行等。
通常情况下,ClassLoader加载类时,会首先调用findClass方法Class> findClass(Stringname),找到我们的类的字节码文件,让后将这个文件包含的字节码加载到内存。但是在我们的ClassLoader抽象类中并没有定义如何去加载,即如何去找到指定的类并且把它的字节码加载到内存中。这个在它的子类URLClassLoader中重写了findClass()方法,在URLClassLoader中通过一个URLClassPath类帮助我们得到需要加载的class文件字节流,而这个URLClassPath定义了到哪里去找这个class文件,如果找到了这个class文件,在读取它的byte字节流,通过调用defineClass()方法来创建Class类对象。
我们在看看URLClassLoader类的构造函数,必须要传入一个URLClassPath对象,也就是必须要指定这个ClassLoader默认到哪个目录下去查找class文件。从URLClassPath的名字中可以发现,它是通过URL的形式来表示ClassPath路径的,也就是我们加载类的路径。
那么如何设置不同ClassLoader 的类加载路径的呢?如下图所示是BootStrapClassLoader、ExtClassLoader、AppClassLoader通过参数的形式指定类加载路径的:
BootStrapClassLoader |
-Xbootclasspath: |
设置BootStrap ClassLoader的搜索路径 |
-Xbootclasspath/a: |
向后追加BootStrap ClassLoader的搜索路径 |
|
-Xbootclasspath/p: |
向前追加BootStrap ClassLoader的搜索路径 |
|
ExtClassLoader |
-Djava.ext.dirs= |
设置ExtClassLoader的搜索路径 |
AppClassLoader |
-Djava.class.path= |
设置AppClassLoader的搜索路径 |
Ø 字节码验证,类加载器需要对类的字节码进行需要检测,以确保格式正确、行为正确。
Ø 类准备,这个阶段用于准备类中定义的字段、方法和实现的接口所必须的数据结构。
Ø 解析,在这个阶段类加载器,加载类所引用(import)的其他所有类,可以用许多方式来引用类,如extends、implements、定义属性、方法类型、方法中使用的变量类型等等。
类中包含的静态代码块都会被执行,在这个阶段的末尾静态字段会被初始化成默认值。
我们经常会用到或扩展ClassLoader,主要会用到如下几个方法,以及他们的重载方法:
Class> defineClass(String name, byte[] b, intoff, int len)
用于将byte字节流解析成JVM能够识别的Class对象,有了这个方法意味着我们不仅仅能够通过class文件实例化对象,还可以通过其他方式如我们通过网络传输一个类的字节码流,拿到这个字节流我们可以创建类的Class对象,然后通过Class对象实例化类对象。
Class> findClass(String name)
defineClass通常会和findClass一起使用,我们通常会直接覆盖ClassLoader父类的findClass方法来实现类的加载规则,从而取得要加载类的字节码。然后调用defineClass来生成类的Class对象。
resolveClass(Class> c)
如果你想要类被加载到JVM中时就被链接(Linking上文有描述),那么可以主动的调用resolveClass方法。当然我们也可以选择让JVM来决定何时链接这个类。
Class> loadClass(String name)
如果你不想重新定义类的加载规则,也没有复杂的处理逻辑,只想在运行时加载自己指定的一个类而已,那么我们此时就可以直接调用ClassLoader的loadClass方法以获取类的Class对象。这个loadClass也有重载方法,我们同样可以决定在什么时候解析这个类。
ClassLoader是一个抽象类,我们想要定义自己的ClassLoader,一般不会继承ClassLoader,而是继承URLClassLoader,因为这个类已经帮助我们实现了大部分工作。
JVM在加载类时默认采用的是双亲委派(或全盘委托)机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
整个JVM中提供了三个ClassLoader类,这个三个ClassLoader可以分为两种类型:BootStrapClassLoader属于一类,其他的属于一类。
BootStrap ClassLoader
它主要加载JVM自身工作需要的类,这个ClassLoader完全是自己控制的,需要加载哪个类,怎么加载都由JVM自己控制,别人也访问不到这个类,所以这个ClassLoader是不遵循上文说的加载规则的,它仅仅就是一个类加载器而已,即没有父加载器,也没有子加载器。
ExtClassLoader
这个类加载器有点特殊,它是JVM自身的一部分,但是它的血统不是很纯正,它并不是JVM亲自实现的。它加载的特定类在System.getProperties(“java.ext.dirs”)目录下。
AppClassLoader
这个类加载器就是用来专门加载应用类的,它的父类是ExtClassLoader。所有在System.getProperties(“java.class.path”)目录下的所有的类都由它来加载,这个目录就是我们常用的classpath。
注意我们如果要实现自定义的类加载器,不管你是直接继承ClassLoader,还是继承URLClassLoader类,或者其他子类,它的父加载器都是AppClassLoader,因为不管调用哪个父构造器,创建的对象都必须最终调用getSystemClassLoader()来作为我们的父类加载器,而该方法获得的恰好就是AppClassLoader。
注意:很多文章在介绍ClassLoader的时候,将BootStrapClassLoader列为我们ExtClassLoader的父加载器,其实BootStrapClassLoader并不属于JVM的类加载器等级层次,因为它并没有遵守类加载规则,另外它也没有子加载器。