ClassLoader介绍

nickelen

贡献于2012-01-04

字数:0 关键词: Java开发

关于 Class 的是如何加载的一直都很模糊,也没去怎么管它,昨天在参加网易的笔试时提到 了这个问题。到网上查了查: 下面这一篇文章是 JavaEYE 里的:http://www.javaeye.com/topic/83978?page=1 ClassLoader 一个经常出现又让很多人望而却步的词,本文将试图以最浅显易懂的方式来讲 解 ClassLoader ,希望能对不了解该机制的朋友起到一点点作用。 要深入了解 ClassLoader ,首先就要知道 ClassLoader 是用来干什么的,顾名思义,它就是 用来加载 Class 文件到 JVM,以供程序使用的。我们知道,java 程序可以动态加载类定义, 而这个动态加载的机制就是通过 ClassLoader 来实现的,所以可想而知 ClassLoader 的重 要性如何。 看到这里,可能有的朋友会想到一个问题,那就是既然 ClassLoader 是用来加载类到 JVM 中的,那么 ClassLoader 又是如何被加载呢?难道它不是 java 的类? 没有错,在这里确实有一个 ClassLoader 不是用 java 语言所编写的,而是 JVM 实现的一部 分,这个 ClassLoader 就是 bootstrap classloader (启动类加载器),这个 ClassLoader 在 JVM 运行的时候加载 java 核心的 API 以满足 java 程序最基本的需求,其中就包括用户 定义的 ClassLoader ,这里所谓的用户定义是指通过 java 程序实现的 ClassLoader ,一个 是 ExtClassLoader ,这个 ClassLoader 是用来加载 java 的扩展 API 的,也就是 /lib/ext 中的 类,一个是 AppClassLoader ,这个 ClassLoader 是用来加载用户机器上 CLASSPATH 设 置目录中的 Class 的,通常在没有指定 ClassLoader 的情况下,程序员自定义的类就由该 ClassLoader 进行加载。 当运行一个程序的时候,JVM 启动,运行 bootstrap classloader ,该 ClassLoader 加载 java 核心 API(ExtClassLoader 和 AppClassLoader 也在此时被加载),然后调用 ExtClassLoader 加载扩展 API,最后 AppClassLoader 加载 CLASSPATH 目录下定义的 Class ,这就是一个 程序最基本的加载流程。 上面大概讲解了一下 ClassLoader 的作用以及一个最基本的加载流程,接下来将讲解一下 ClassLoader 加载的方式,这里就不得不讲一下 ClassLoader 在这里使用了双亲委托模式进 行类加载。 每一个自定义 ClassLoader 都必须继承 ClassLoader 这个抽象类,而每个 ClassLoader 都 会有一个 parent ClassLoader ,我们可以看一下 ClassLoader 这个抽象类中有一个 getParent() 方法,这个方法用来返回当前 ClassLoader 的 parent ,注意,这个 parent 不是 指的被继承的类,而是在实例化该 ClassLoader 时指定的一个 ClassLoader ,如果这个 parent 为 null ,那么就默认该 ClassLoader 的 parent 是 bootstrap classloader ,这个 parent 有什么用呢? 我们可以考虑这样一种情况,假设我们自定义了一个 ClientDefClassLoader ,我们使用这个 自定义的 ClassLoader 加载 java.lang.String ,那么这里 String 是否会被这个 ClassLoader 加载呢?事实上 java.lang.String 这个类并不是被这个 ClientDefClassLoader 加载,而是由 bootstrap classloader 进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为 在任何一个自定义 ClassLoader 加载一个类之前,它都会先委托它的父亲 ClassLoader 进 行加载,只有当父亲 ClassLoader 无法加载成功后,才会由自己加载,在上面这个例子里, 因为 java.lang.String 是属于 java 核心 API 的一个类,所以当使用 ClientDefClassLoader 加载它的时候,该 ClassLoader 会先委托它的父亲 ClassLoader 进行加载,上面讲过,当 ClassLoader 的 parent 为 null 时,ClassLoader 的 parent 就是 bootstrap classloader ,所 以在 ClassLoader 的最顶层就是 bootstrap classloader ,因此最终委托到 bootstrap classloader 的时候,bootstrap classloader 就会返回 String 的 Class 。 我们来看一下 ClassLoader 中的一段源代码: Java 代码 1. protected synchronized Class loadClass(String name, boolean resolve) 2. throws ClassNotFoundException 3. { 4. // 首先检查该 name 指定的 class 是否有被加载 5. Class c = findLoadedClass(name); 6. if (c == null ) { 7. try { 8. if (parent != null ) { 9. // 如果 parent 不为 null ,则调用 parent 的 loadClass 进行加载 10. = parent.loadClass(name, false ); 11. } else { 12. //parent 为 null ,则调用 BootstrapClassLoader 进行加载 13. c = findBootstrapClass0(name); 14. } 15. } catch (ClassNotFoundException e) { 16. // 如果仍然无法加载成功,则调用自身的 findClass 进行加载 17. c = findClass(name); 18. } 19. } 20. if (resolve) { 21. resolveClass(c); 22. } 23. return c; 24. } 从上面一段代码中,我们可以看出一个类加载的大概过程与之前我所举的例子是一样的,而 我们要实现一个自定义类的时候,只需要实现 findClass 方法即可。 为什么要使用这种双亲委托模式呢? 第一个原因就是因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子 ClassLoader 再加载一次。 第二个原因就是考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以 随时使用自定义的 String 来动态替代 java 核心 api 中定义类型,这样会存在非常大的安全 隐患,而双亲委托的方式,就可以避免这种情况,因为 String 已经在启动时被加载,所以 用户自定义类是无法加载一个自定义的 ClassLoader 。 上面对 ClassLoader 的加载机制进行了大概的介绍,接下来不得不在此讲解一下另外一个 和 ClassLoader 相关的类,那就是 Class 类,每个被 ClassLoader 加载的 class 文件,最终 都会以 Class 类的实例被程序员引用,我们可以把 Class 类当作是普通类的一个模板,JVM 根据这个模板生成对应的实例,最终被程序员所使用。 我们看到在 Class 类中有个静态方法 forName ,这个方法和 ClassLoader 中的 loadClass 方法的目的一样,都是用来加载 class 的,但是两者在作用上却有所区别。 Class loadClass(String name) Class loadClass(String name, boolean resolve) 我们看到上面两个方法声明,第二个方法的第二个参数是用于设置加载类的时候是否连接该 类,true 就连接,否则就不连接。 说到连接,不得不在此做一下解释,在 JVM 加载类的时候,需要经过三个步骤,装载、连 接、初始化。装载就是找到相应的 class 文件,读入 JVM,初始化就不用说了,最主要就说 说连接。 连接分三步,第一步是验证 class 是否符合规格,第二步是准备,就是为类变量分配内存同 时设置默认初始值,第三步就是解释,而这步就是可选的,根据上面 loadClass 方法的第二 个参数来判定是否需要解释,所谓的解释根据《深入 JVM》这本书的定义就是根据类中的 符号引用查找相应的实体,再把符号引用替换成一个直接引用的过程。有点深奥吧,呵呵, 在此就不多做解释了,想具体了解就翻翻《深入 JVM 吧》,呵呵,再这样一步步解释下去, 那就不知道什么时候才能解释得完了。 我们再来看看那个两个参数的 loadClass 方法,在 JAVA API 文档中,该方法的定义是 protected ,那也就是说该方法是被保护的,而用户真正应该使用的方法是一个参数的那个, 一个参数的 loadclass 方法实际上就是调用了两个参数的方法,而第二个参数默认为 false , 因此在这里可以看出通过 loadClass 加载类实际上就是加载的时候并不对该类进行解释,因 此也不会初始化该类。而 Class 类的 forName 方法则是相反,使用 forName 加载的时候就 会将 Class 进行解释和初始化,forName 也有另外一个版本的方法,可以设置是否初始化 以及设置 ClassLoader ,在此就不多讲了。 不知道上面对这两种加载方式的解释是否足够清楚,就在此举个例子吧,例如 JDBC DRIVER 的加载,我们在加载 JDBC 驱动的时候都是使用的 forName 而非是 ClassLoader 的 loadClass 方法呢?我们知道,JDBC 驱动是通过 DriverManager ,必须在 DriverManager 中注册,如果驱动类没有被初始化,则不能注册到 DriverManager 中,因此必须使用 forName 而不能用 loadClass 。 通过 ClassLoader 我们可以自定义类加载器,定制自己所需要的加载方式,例如从网络加 载,从其他格式的文件加载等等都可以,其实 ClassLoader 还有很多地方没有讲到,例如 ClassLoader 内部的一些实现等等,本来希望能够讲得简单易懂一点,可是结果自己看回头 好像感觉并不怎么样,郁闷,看来自己的文笔还是差太多了,希望能够给一些有需要的朋友 一点帮助吧。 另外一篇是:http://dev.csdn.net/article/68/68103.shtm 静态库静态库静态库静态库、、、、动态连接库动态连接库动态连接库动态连接库 程序编制一般需经编辑、编译、连接、加载和运行几个步骤。在我们的应用中,有一些公共 代码是需要反复使用,就把这些代码编译为 “库”文件;在连接步骤中,连接器将从库文件取 得所需的代码,复制到生成的可执行文件中。这种库称为静态库,其特点是可执行文件中包 含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。 为了克服这个缺点可以采用动态连接库。这个时候连接器仅仅是在可执行文件中打上标志, 说明需要使用哪些动态连接库;当运行程序时,加载器根据这些标志把所需的动态连接库加 载到内存。 另外在当前的编程环境中,一般都提供方法让程序在运行的时候把某个特定的动态连接库加 载并运行,也可以将其卸载(例如 Win32 的 LoadLibrary()&FreeLibrary() 和 Posix 的 dlopen()&dlclose() )。这个功能被广泛地用于在程序运行时刻更新某些功能模块或者是程 序外观。 What is ClassLoader? 与普通程序不同的是,Java 程序(class 文件)并不是本地的可执行程序。当运行 Java 程 序时,首先运行 JVM(Java 虚拟机),然后再把 Java class 加载到 JVM 里头运行,负责 加载 Java class 的这部分就叫做 Class Loader 。 JVM 本身包含了一个 ClassLoader 称为 Bootstrap ClassLoader ,和 JVM 一样,Bootstrap ClassLoader 是用本地代码实现的,它负责加载核心 Java Class (即所有 java.* 开头的类)。 另外 JVM 还会提供两个 ClassLoader ,它们都是用 Java 语言编写的,由 Bootstrap ClassLoader 加载;其中 Extension ClassLoader 负责加载扩展的 Java class (例如所有 javax.* 开头的类和存放在 JRE 的 ext 目录下的类), Application ClassLoader 负责加载应 用程序自身的类。 When to load the class? 什么时候 JVM 会使用 ClassLoader 加载一个类呢?当你使用 java 去执行一个类,JVM 使 用 Application ClassLoader 加载这个类;然后如果类 A 引用了类 B,不管是直接引用还是 用 Class.forName() 引用,JVM 就会找到加载类 A 的 ClassLoader ,并用这个 ClassLoader 来加载类 B。 Why use your own ClassLoader? 似乎 JVM 自身的 ClassLoader 已经足够了,为什么我们还需要创建自己的 ClassLoader 呢? 因为 JVM 自带的 ClassLoader 只是懂得从本地文件系统加载标准的 java class 文件,如果 编写你自己的 ClassLoader ,你可以做到: 1)在执行非置信代码之前,自动验证数字签名 2)动态地创建符合用户特定需要的定制化构建类 3)从特定的场所取得 java class ,例如数据库中 4) 等等 事实上当使用 Applet 的时候,就用到了特定的 ClassLoader ,因为这时需要从网络上加载 java class ,并且要检查相关的安全信息。 目前的应用服务器大都使用了 ClassLoader 技术,即使你不需要创建自己的 ClassLoader , 了解其原理也有助于更好地部署自己的应用。 ClassLoader Tree & Delegation Model 当你决定创建你自己的 ClassLoader 时,需要继承 java.lang.ClassLoader 或者它的子类。 在实例化每个 ClassLoader 对象时,需要指定一个父对象;如果没有指定的话,系统自动 指定 ClassLoader.getSystemClassLoader() 为父对象。如下图: 在 Java 1.2 后,java class 的加载采用所谓的委托模式(Delegation Modle ),当调用一个 ClassLoader.loadClass() 加载一个类的时候,将遵循以下的步骤: 1)检查这个类是否已经被加载进来了? 2)如果还没有加载,调用父对象加载该类 3)如果父对象无法加载,调用本对象的 findClass() 取得这个类。 所以当创建自己的 Class Loader 时,只需要重载 findClass() 这个方法。 Unloading? Reloading? 当一个 java class 被加载到 JVM 之后,它有没有可能被卸载呢?我们知道 Win32 有 FreeLibrary() 函数,Posix 有 dlclose() 函数可以被调用来卸载指定的动态连接库,但是 Java 并没有提供一个 UnloadClass() 的方法来卸载指定的类。 在 Java 中,java class 的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。 既然是一种优化方法,那么就完全是 JVM 自行决定如何实现,对 Java 开发人员来说是完 全透明的。 在什么时候一个 java class/interface 会被卸载呢?Sun 公司的 原话 是这么说的:"class or interface may be unloaded if and only if its class loader is unreachable. Classes loaded by the bootstrap loader may not be unloaded." 事实上我们关心的不是如何卸载类的,我们关心的是如何更新已经被加载了的类从而更新应 用的功能。JSP 则是一个非常典型的例子,如果一个 JSP 文件被更改了,应用服务器则需 要把更改后的 JSP 重新编译,然后加载新生成的类来响应后继的请求。 其实一个已经加载的类是无法被更新的,如果你试图用同一个 ClassLoader 再次加载同一 个类,就会得到异常(java.lang.LinkageError: duplicate class definition ),我们只能够重 新创建一个新的 ClassLoader 实例来再次加载新类。至于原来已经加载的类,开发人员不 必去管它,因为它可能还有实例正在被使用,只要相关的实例都被内存回收了,那么 JVM 就会在适当的时候把不会再使用的类卸载。

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 7 人已下载

下载文档

相关文档