什么是ClassLoader
ClassLoader是一个抽象类,我们用它的实例对象来装载类,它负责将 Java 字节码装载到 JVM 中, 并使其成为 JVM 一部分。 JVM 的类动态装载技术能够在运行时刻动态地加载或者替换系统的某些功能模块,而不影响系统其他功能模块的正常运行。一般是通过类名读入一个class文件来装载这个类,(其它加载形式暂时没有研究过)。
JAVA一般用三个class loader完成类的装载任务(自己定义的class loader出外),它们是Bootstrap Loader、ExtClassLoader和AppClassLoader,最后一个也被SUN称为system class loader。它们的组织方式是:先由Bootstrap Loader生成ExtClassLoader,并设置ExtClassLoader的parent为null(其实准确得说其parent是Bootstrap Loader,但因为Bootstrap Loader由C++写成,并不是逻辑意义上的JAVA类,所以只能为null);然后Bootstrap Loader生成AppclassLoader,设置其parent为ExtClassLoader。
类的装载采用的是代理模式:装载一个类时先由AppClassLoader或自己定义的class loader请求其parent装载,parent再请求它的parent,这样到了顶级,也就是Bootstrap Loader,如果parent looder找到了要装载的类,就由parent loader装载,否则就由它的"下级"去干这件事。这样就一级级委托知道把类找到装载完毕。要说明的是ExtClassLoader和 AppClassLoader都是URLClassLoader的子类,而URLClassLoader可由一个URL对象确定它寻找类的路径,我们可以通过下面的方法发现前述的三种class loader的搜寻路径:
ClassLoader装载过程
类装载就是寻找一个类或是一个接口的字节码文件并通过解析该字节码来构造代表这个类或是这个接口的 class 对象的过程。在Java 中,类装载器把一个类装入 Java 虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
1. 装载:查找和导入类或接口的字节码;
2. 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
l 校验:检查导入类或接口的二进制数据的正确性;
l 准备:给类的静态变量分配并初始化存储空间;
l 解析:将符号引用转成直接引用;
3. 初始化:激活类的静态变量的初始化 Java 代码和静态 Java 代码块。
装载的实现
JVM 中类的装载是由 ClassLoader 和它的子类来实现的。 Java ClassLoader 是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入 Java 字节码。
在 Java 中, ClassLoader 是一个抽象类,它在包 java.lang 中。可以这样说,只要了解了 ClassLoader 中的一些重要的方法,再结合上面所介绍的 JVM 中类装载的具体的过程,对动态装载类这项技术就有了一个比较大概的掌握,这些重要的方法包括以下几个:
1. loadCass 方法: loadClass(String name ,boolean resolve) 其中 name 参数指定了 JVM 需要的类的名称 , 该名称以类的全限定名表示,如 Java.lang.Object ; resolve 参数告诉方法是否需要解析类,在初始化类之前,应考虑类解析,并不是所有的类都需要解析,如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。这个方法是 ClassLoader 的入口点。
2. defineClass 方法 这个方法接受类文件的字节数组并把它转换成 Class 对象。字节数组可以是从本地文件系统或网络装入的数据。它把字节码分析成运行时数据结构、校验有效性等等。
3. findSystemClass 方法 findSystemClass 方法从本地文件系统装入 Java 字节码。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将字节数组转换成 Class 对象。当运行 Java 应用程序时 , 这是 JVM 正常装入类的缺省机制。
4. resolveClass 方法 resolveClass(Class c) 方法解析装入的类,如果该类已经被解析过那么将不做处理。当调用 loadClass 方法时 , 通过它的 resolve 参数决定是否要进行解析。
5. findLoadedClass 方法 当调用 loadClass 方法装入类时 , 调用 findLoadedClass 方法来查看 ClassLoader 是否已装入这个类 ,如果已装入 , 那么返回 Class 对象 , 否则返回 NULL 。如果强行装载已存在的类 , 将会抛出链接错误。
java.lang.Class类
某 个类的所有实例内部都有一个引用,指向该类对应的Class的实例的位置,每个java类对应的Class实例可以当作是类在内存中的代理人。所以当要获 得类的信息(如有哪些类变量,有哪些方法)时,都可以让类对应的Class的实例代劳.java的Reflection机制就大量的使用这种方法来实现。 但是Class类无法手工实例化,当载入任意类的时候自动创建一个该类对应的Class的实例。每个java类都是由某个classLoader (ClassLoader的实例)来载入的,因此Class类别的实例中都会他的ClassLoader的实例的引用。可以通过 getClass.getClassLoader()得到CLassLoader的实例。
java动态载入class的两种方式:
1)implicit隐式,即利用实例化才载入的特性来动态载入class
2)explicit显式方式,又分两种方式:
a)java.lang.Class的forName()方法
b)java.lang.ClassLoader的loadClass()方法
各种java类由哪些classLoader加载?
1)java类可以通过实例.getClass.getClassLoader()得到
2)接口由AppClassLoader(SystemClassLoader)载入 ,SystemClassLoader:可以由ClassLoader.getSystemClassLoader()获得实例
3)ClassLoader类由bootstrap loader载入
ClassLoader hierachy:
1)jvm初始化产生bootstrap loader。并设定它的父ClassLoader为null
2)bootstrap loader建立AppClassLoader,载入 运行java.exe时 的-cp或-classpath中的类(每个运行中的线程都有一个成员contextClassLoader,用来在运行时动态地载入其它类系统默认的 contextClassLoader是systemClassLoader)
Java 类装载的代理结构
根 (Bootstrap) 装载器:该装载器没有父装载器,它是 JVM 实现的一部分,从 sun.boot.class.path 装载运行时库的核心代码。
扩展 (Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯 Java代码实现的,它从 java.ext.dirs ( 扩展目录 ) 中装载代码。
系统 (System or Application) 装载器:装载器为扩展装载器,我们都知道在安装 JDK 的时候要设置环境变量 (CLASSPATH ) ,这个类装载器就是从 java.class.path(CLASSPATH 环境变量 ) 中装载代码的,它也是用纯 Java 代码实现的,同时还是用户自定义类装载器的缺省父装载器。
小应用程序 (Applet) 装载器:父装载器为系统装载器,它从用户指定的网络上的特定目录装载小应用程序代码。
java中的代理结构是自上而下查找类的,这与很多web装载器不同。
tomcat中的实现的子ClassLoader的结构
Tomcat Server在启动的时候将构造一个ClassLoader树,以保证模块的类库是私有的
tomcat server的classloader结构如下:
---------------------------+
| bootstrap |
| | |
| system |
| | |
| common |
| / / |
| catalina shared |
| / / |
| webapp1 webapp2 |
---------------------------+
其中:
- Bootstrap - 载入JVM自带的类和$JAVA_HOME/jre/lib/ext/*.jar
- System - 载入$CLASSPATH/*.class
- Common - 载入$CATALINA_HOME/common/...,它们对TOMCAT和所有的WEB APP都可见
- Catalina - 载入$CATALINA_HOME/server/...,它们仅对TOMCAT可见,对所有的WEB APP都不可见
- Shared - 载入$CATALINA_HOME/shared/...,它们仅对所有WEB APP可见,对TOMCAT不可见(也不必见)
- WebApp? - 载入ContextBase?/WEB-INF/...,它们仅对该WEB APP可见
ClassLoader被组织成树形,一般的工作原理是:
1) 线程需要用到某个类,于是contextClassLoader被请求来载入该类
2) contextClassLoader请求它的父ClassLoader来完成该载入请求
3) 如果父ClassLoader无法载入类,则contextClassLoader试图自己来载入
注意:WebApp?ClassLoader的工作原理和上述有少许不同:
它先试图自己载入类(在ContextBase?/WEB-INF/...中载入类),如果无法载入,再请求父ClassLoader完成