ClassLoader分析

转载 2006年05月31日 17:17:00
1           前言
ClassLoader 是 Java 虚拟机 (JVM) 的类装载子系统,它负责将 Java 字节码装载到 JVM 中, 并使其成为 JVM 一部分。 JVM 的类动态装载技术能够在运行时刻动态地加载或者替换系统的某些功能模块 , 而不影响系统其他功能模块的正常运行。本文将分析 JVM 中的类装载系统,探讨 JVM 中类装载的原理、实现以及应用。
2           装载过程简介
类装载就是寻找一个类或是一个接口的字节码文件并通过解析该字节码来构造代表这个类或是这个接口的 class 对象的过程。在 Java 中,类装载器把一个类装入 Java 虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
1.        装载:查找和导入类或接口的字节码;
2.        链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
l        校验:检查导入类或接口的二进制数据的正确性;
l        准备:给类的静态变量分配并初始化存储空间;
l        解析:将符号引用转成直接引用;
3.        初始化:激活类的静态变量的初始化 Java 代码和静态 Java 代码块。
至于在类装载和虚拟机启动的过程中的具体细节和可能会抛出的错误,请参看《 Java 虚拟机规范》以及《深入 Java 虚拟机》。 由于本文的讨论重点不在此就不再多叙述。
2.1           装载的实现
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 。如果强行装载已存在的类 , 将会抛出链接错误。
2.2           装载的应用
一般来说,我们实现自定义的 ClassLoader 需要继承抽象类 java.lang.ClassLoader ,其中必须实现的方法是 loadClass(String name) ,对于这个方法需要实现如下操作:
(1) 确认类的名称;
(2) 检查请求要装载的类是否已经被装载;
(3) 检查请求加载的类是否是系统类;
(4) 尝试从类装载器的存储区获取所请求的类;
(5) 在虚拟机中定义所请求的类;
(6) 解析所请求的类;
(7) 返回所请求的类。
所有的 Java 虚拟机都包括一个内置的类装载器,这个内置的类库装载器被称为根装载器 (bootstrap ClassLoader) 。根装载器的特殊之处是它只能够装载基本的 Java 类,如 rt.jar 中的 class 。当应用程序可以使用用户自定义的 ClassLoader 来加载特定 ClassPath 下的类。下面的例子是 JDK5.0 的 URLClassLoader 的实现。
public class URLClassLoader extends SecureClassLoader {
……
protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
       try {
           return (Class)
              AccessController.doPrivileged(new PrivilegedExceptionAction() {
                  public Object run() throws ClassNotFoundException {
                                                               // 由类的全限定名得到物理路径
                     String path = name.replace('.', '/').concat(".class");
                                                               // URLs ClassPath 中取得相应的字节码
                     Resource res = ucp.getResource(path, false);
                     if (res != null) {
                         try {
                                                             // Java 字节码创建一个 Class 对象
                            return defineClass(name, res);
                         } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                         }
                     } else {
                         throw new ClassNotFoundException(name);
                     }
                  }
              }, acc);
       } catch (java.security.PrivilegedActionException pae) {
           throw (ClassNotFoundException) pae.getException();
       }
    }
……
}
 
2.3           Java 虚拟机的类装载原理
前面我们已经知道,一个 Java 应用程序使用两种类型的类装载器:根装载器 (bootstrap) 和用户定义的装载器 (user-defined) 。根装载器是 Java 虚拟机实现的一部分。根装载器以某种默认的方式将类装入,包括 Java API 的类。在运行期间,一个 Java 程序能使用用户自己定义的类装载器。根装载器是虚拟机固有的一部分,而用户定义的类装载器则不是,它是用 Java 语言写的,被编译成 class 文件之后然后再被装入到虚拟机,并像其它的任何对象一样可以被实例化。 Java 类装载器的体系结构如下所示:
Java 的类装载的体系结构
Java 的类装载模型是一种代理 (delegation) 模型。当 JVM 要求类装载器 CL(ClassLoader) 装载一个类时 ,CL 首先将这个类装载请求转发给他的父装载器。只有当父装载器没有装载并无法装载这个类时 ,CL 才获得装载这个类的机会。这样 , 所有类装载器的代理关系构成了一种树状的关系。树的根是类的根装载器 (bootstrap ClassLoader) , 在 JVM 中它以 "null" 表示。除根装载器以外的类装载器有且仅有一个父装载器。在创建一个装载器时 , 如果没有显式地给出父装载器 , 那么 JVM 将默认系统装载器为其父装载器。 Java 的基本类装载器代理结构如图 2 所示:
Java 类装载的代理结构
下面针对各种类装载器分别进行详细的说明。
根 (Bootstrap) 装载器:该装载器没有父装载器,它是 JVM 实现的一部分,从 sun.boot.class.path 装载运行时库的核心代码。
 
扩展 (Extension) 装载器:继承的父装载器为根装载器,不像根装载器可能与运行时的操作系统有关,这个类装载器是用纯 Java 代码实现的,它从 java.ext.dirs ( 扩展目录 ) 中装载代码。
系统 (System or Application) 装载器:装载器为扩展装载器,我们都知道在安装 JDK 的时候要设置环境变量 (CLASSPATH ) ,这个类装载器就是从 java.class.path(CLASSPATH 环境变量 ) 中装载代码的,它也是用纯 Java 代码实现的,同时还是用户自定义类装载器的缺省父装载器。
小应用程序 (Applet) 装载器:父装载器为系统装载器,它从用户指定的网络上的特定目录装载小应用程序代码。
在设计一个类装载器的时候,应该满足以下两个条件:
对于相同的类名,类装载器所返回的对象应该是同一个类对象
如果类装载器 CL1 将装载类 C 的请求转给类装载器 CL2 ,那么对于以下的类或接口, CL1 和 CL2 应该返回同一个类对象:
a)  S 为 C 的直接超类;
b)  S 为 C 的直接超接口;
c)  S 为 C 的成员变量的类型;
d)  S 为 C 的成员方法或构建器的参数类型;
e)  S 为 C 的成员方法的返回类型。
每个已经装载到 JVM 中的类对象都含有装载它的类装载器的信息。 Class 类的方法 getClassLoader 可以得到装载这个类的类装载器。一个类装载器认识的类包括它的父装载器认识的类和它自己装载的类,由此可见类装载器认识的类是它自己装载的类的超集。注意,我们可以得到类装载器的有关的信息,但是已经装载到 JVM 中的类是不能更改它的类装载器的。
Java 中的类的装载过程也就是代理装载的过程。比如: Web 浏览器中的 JVM 需要装载一个小应用程序 TestApplet 。 JVM 调用小应用程序装载器 ACL(Applet ClassLoader) 来完成装载。 ACL 首先请求它的父装载器 , 即系统装载器装载 TestApplet 是否装载了这个类,由于 TestApplet 不在系统装载器的装载路径中 , 所以系统装载器没有找到这个类 , 也就没有装载成功。接着 ACL 自己装载 TestApplet 。 ACL 通过网络成功地找到了 TestApplet.class 文件并将它导入到了 JVM 中。在装载过程中 , JVM 发现 TestAppet 是从超类 java.applet.Applet 继承的。所以 JVM 再次调用 ACL 来装载 java.applet.Applet 类。 ACL 又再次按上面的顺序装载 Applet 类 , 结果 ACL 发现他的父装载器已经装载了这个类 , 所以 ACL 就直接将这个已经装载的类返回给了 JVM , 完成了 Applet 类的装载。接下来 ,Applet 类的超类也一样处理。最后 , TestApplet 及所有有关的类都装载到了 JVM 中。
2.4           总结
    类的动态装载机制是JVM的一项核心技术, 也是容易被忽视而引起很多误解的地方。本文介绍了JVM中类装载的原理、实现以及应用,分析了ClassLoader的结构、用途以及如何利用自定义的ClassLoader装载并执行Java类,希望能使大家对JVM中的类装载有一个比较深入的理解。

Tomcat源码分析之ClassLoader部分的设计详细分析

读Tomcat的源码也算是有一段时间了吧,感觉读的也是断断续续的,这次写一篇比较综合性的吧,来看看Tomcat的整体ClassLoader体系的设计。。。。 在具体的涉及到源码之前,先来一张图来整体的...
  • fjs_cloud
  • fjs_cloud
  • 2014年03月16日 19:35
  • 1865

深度分析 Java 的 ClassLoader 机制(源码级别)

Java中的所有类,必须被装载到jvm中才能运行,这个装载工作是由jvm中的类装载器完成的,类装载器所做的工作实质是把类文件从硬盘读取到内存中,JVM在加载类的时候,都是通过ClassLoader的l...
  • Jason_huzhe
  • Jason_huzhe
  • 2016年05月05日 09:39
  • 625

Android中ClassLoader源码解析之真的是你认为的ClassLoader

1.前言 首先,阅读本文章之前,需要了解java中的ClassLoader的基本原理,包括java中的三级ClassLoader机制以及ClassLoader的委托机制,否则下面的内容会不知道在讲什么...
  • u010014658
  • u010014658
  • 2016年09月19日 16:54
  • 1887

自定义ClassLoader,让spring加载外部的配置文件和类

今天同事遇到一个需求: 在外部以jar包的形式存放若干个插件,其中包含插件的类,以及spring配置文件;jar包不在classpath里 要实现这个需求,需要用到自定义的ClassLoader,...
  • kyfxbl
  • kyfxbl
  • 2013年09月24日 11:14
  • 3841

深入分析ClassLoader

why?ClassLoader,即java类加载器,主要作用是将class加载到JVM内,同时它还要考虑class由谁来加载。在说java的类加载机制之前,还是像前面的博客一样,先说说为什么要知道ja...
  • tonytfjing
  • tonytfjing
  • 2015年08月02日 22:52
  • 7664

java反射机制剖析(二)— Class Loader

上一篇博客简要的提了一下java反射机制中涉及到的一些相关知识,那么ClassLoader就是其中之一。本篇博客就详细的对ClassLoader做一个相对深入的了解。作为了解需要知道的是,其实类类型(...
  • zhuojiajin
  • zhuojiajin
  • 2014年08月31日 18:34
  • 3215

Android插件化学习之路(二)之ClassLoader完全解析

Java代码都是写在Class里面的,程序运行在虚拟机上时,虚拟机需要把需要的Class加载进来才能创建实例对象并工作,而完成这一个加载工作的角色就是ClassLoader。类加载器ClassLoad...
  • u012124438
  • u012124438
  • 2016年11月19日 22:53
  • 3017

ClassLoader 详解及用途(写的不错)

ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。 1.1 几个相关概念Class...
  • lsm135
  • lsm135
  • 2016年08月06日 21:50
  • 567

一看你就懂,超详细java中的ClassLoader详解

ClassLoader翻译过来就是类加载器,普通的java开发者其实用到的不多,但对于某些框架开发者来说却非常常见。理解ClassLoader的加载机制,也有利于我们编写出更高效的代码。ClassLo...
  • briblue
  • briblue
  • 2017年02月10日 19:26
  • 39572

java反射中,Class.forName和classloader的区别(代码说话)

java中class.forName()和classLoader都可用来对类进行加载。 class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的st...
  • qq_27093465
  • qq_27093465
  • 2016年08月20日 17:14
  • 14894
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ClassLoader分析
举报原因:
原因补充:

(最多只允许输入30个字)