没想到自己的这篇总结会隔了这么久才发上来。一来工作比较忙,二来孩子有点闹,回家干不了啥活。于是就拖了这么久。其实,说起来网上介绍 classloader的文章很多,没必要再发一篇。不过,后来想想,还是自己再写一篇。一是对自己学习的一个总结,二是给大家多一个角度来学习 classloader。
开篇先列一下我的参考文章吧。网上相关文章太多,看了好久,筛选出来这几篇。首先就是最重要的IBM的developwork上的两个系列文章。我觉得 IBM的这个技术网站做的太棒了,很多文章都是学习的好帮手。我遇到什么问题都喜欢先去搜一下。然后还有其他几篇不错的文章,包括robbin的一篇。还有就是《深入JVM》这本经典的书了。建议有时间,学习JAVA的人都应该去看看。
http://www.ibm.com/developerworks/cn/java/j-dclp1/
http://www.ibm.com/developerworks/cn/java/j-dyn0429/
http://www.iteye.com/topic/136427
http://www.iteye.com/topic/11?page=1
http://www.jdon.com/article/15456.html
http://blog.bcchinese.net/shiaohuazhang/archive/2004/10/13/2715.aspx
一 classloader基础知识
我们要想从一个class文件得到一个在JVM中可以使用的类,需要经过一个复杂的过程。首先,JVM通过classloader机制装载一个JAVA类型,然后连接,然后初始化。即装载-->连接(验证-->准备-->解析)-->初始化。经过这样一个过程,我们才能得到一个在 JVM中可以使用的JAVA类。连接包括验证,准备,解析。下面这个图表示了这个过程。从IBM的文章中截过来的,所以是英文,不好意思了。
装载就是把二进制形式的JAVA类型读入到JVM中。它为类对象提供了非常基本的内存结构。根据JAVA API的定义:类装载器是负责装载类的对象。ClassLoader 类是一个抽象类。如果给定类的二进制名字,那么类装载器会试图查找或生成构成类定义的数据。一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的“类文件”。每个Class对象都包含一个对定义它的 ClassLoader 的引用。数组类的 Class 对象不是由类装载器创建的,而是由 Java 运行时根据需要自动创建。数组类的类装载器由 Class.getClassLoader()返回,该装载器与其元素类型的类装载器是相同的;如果该元素类型是基本类型,则该数组类没有类装载器。
装载由三个基本动作组成:
1 通过该类型的完全限定名产生一个代表该类型的二进制数据流
2 解析这个二进制数据流为方法区内的内部数据结构
3 创建一个表示该类型的java.lang.Class类的实例
JVM的classloader可以采用两种方式来装载JAVA类。一种是预先装载的方式来装载类;一种是用的时候再加载。但无论预先装载还是用的时候再 装载,装载过程中的错误,只有当程序中第一次主动使用该类时才报告错误:LinkageError等。如果该类一直没有被使用,那么就一直不会报告错误。
连接就是把这种已经读入得二进制形式的类型数据合并到JVM得运行时状态中去。验证确保JAVA类型数据格式正确并且适于JVM使用。准备负责为该类型分 配它所需得内存。解析负责把常量池中得符号引用转换成直接引用。解析这一步可以推迟到程序运行时真正使用这个符号时再去解析它。
初始化是在连接之后,JVM第一次主动使用该类型时进行的。所谓主动使用包括以下几种情况:
创建类的新实例时(new指令或通过不明确的创建,反射,克隆或反序列化)
调用类的静态方法时
使用类的静态字段,或对该字段赋值时(final修饰的静态字段除外)
初始化某个类的子类时
JVM启动时某个被标明为启动类的类即含有main()方法的类
类的初始化要求其超类先被初始化,但对于接口来说,并不是这样。只有当接口中所声明的非常量字段被使用时,该接口才被初始化。也就是说接口初始化时并不要求其超类先初始化。在准备阶段,JVM为类变量分配内存,设置默认初始值。这个默认值是JAVA语言对每种类型的默认值。而在初始化阶段会给类变量赋予真 正的初始值。这个初始值才是程序员编程时指定的类变量的初始值。当一个类有超类的时候,它会先初始化这个超类。初始化之后,程序可以访问类的静态字段,调用类的静态方法,或创建类的实例。
通过连接,可以把类的符号引用转换成直接引用,这时候要检查正确性和权限。JAVA语言的动态扩展性就是在连接过程中体现的。JAVA程序可以在运行时决 定连接哪个类型。这样就实现了JAVA的动态扩展性。有两种方法:一种是使用java.lang.Class的forName()方法;一种是采用用户自 定义的类装载器的loadClass()方法。第一种方法的优点在于它得到的类型一定是装载并初始化的;而第二种的只装载不一定初始化。但第二种方法更灵 活,可以实现一些特定的功能。比如说安全性或需要加载一些特定的类型如从网上下载的类型。
每一个classloader都维护属于自己的命名空间,在同一个命名空间里,两个类名字不能相同。命名空间由所有以此装载器为创始类装载器的类组成。不同命名空间的两个类是不可见的,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。为了实现JAVA的安全沙箱模型顶层的类加载器安全机制,classloader采用双亲委派模型。(JAVA的安全沙箱机制在前一篇关于classloader的预备知识的文章里有介绍)
一个例子,测试你所使用的JVM的ClassLoader
java 代码
-
- public class LoaderSample1 {
- public static void main(String[] args) {
- Class c;
- ClassLoader cl;
- cl = ClassLoader.getSystemClassLoader();
- System.out.println(cl);
- while (cl != null) {
- cl = cl.getParent();
- System.out.println(cl);
- }
- try {
- c = Class.forName("java.lang.Object");
- cl = c.getClassLoader();
- System.out.println("java.lang.Object's loader is " + cl);
- c = Class.forName("LoaderSample1");
- cl = c.getClassLoader();
- System.out.println("LoaderSample1's loader is " + cl);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
在我的机器上(Sun Java 1.4.2)的运行结果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null
java.lang.Object's loader is null
LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f
第一行表示,系统类装载器实例化自类sun.misc.Launcher$AppClassLoader
第二行表示,系统类装载器的parent实例化自类sun.misc.Launcher$ExtClassLoader
第三行表示,系统类装载器parent的parent为bootstrap
第四行表示,核心类java.lang.Object是由bootstrap装载的
另一个例子,演示了一个命名空间的类如何使用另一命名空间的类。在例子中,LoaderSample2由系统类装载器装载,LoaderSample3由自定义的装载器loader负责装载,两个类不在同一命名空间,但LoaderSample2得到了LoaderSample3所对应的Class对象的reference,所以它可以访问LoaderSampl3中公共的成员(如age)。运行:java LoaderSample2
java 代码
-
- import java.net.*;
- import java.lang.reflect.*;
- public class LoaderSample2 {
- public static void main(String[] args) {
- try {
- String path = System.getProperty("user.dir");
- URL[] us = {new URL("file://" + path + "/sub/")};
- ClassLoader loader = new URLClassLoader(us);
- Class c = loader.loadClass("LoaderSample3");
- Object o = c.newInstance();
- Field f = c.getField("age");
- int age = f.getInt(o);
- System.out.println("age is " + age);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
-
-
- public class LoaderSample3 {
- static {
- System.out.println("LoaderSample3 loaded");
- }
- public int age = 30;
- }
LoaderSample3 loaded
age is 30
从运行结果中可以看出,在类LoaderSample2中可以创建处于另一命名空间的类LoaderSample3中的对象并可以访问其公共成员age。
二 classloader实现机制
类装载器采用双亲委派模型。在建立用户自定义的类装载器时可以指定其双亲。如果没有指定,则默认的是把系统类装载器作为其双亲。如果向用户自定义的装载器的构造方法里传递null,则引导装载器就是双亲。
classloader依次从缓存,双亲,自己来寻找类。classloader首先判断要求它装入的类是否与过去装入的类相同。如果相同,就返回上次返回的类(即保存在缓存中的类)。如果不是,就把装入类的机会交给父类。这两步递归地以深度优先的方式重复。如果父类返回 null(或抛出 ClassNotFoundException
),那么类装入器会在自己的路径中寻找类的源。
因为父类类装入器总是先得到装入类的机会,所以classloader装入的类最靠近根。这意味着所有核心引导类都是由引导装入器装入的,这就保证装入了类(例如 java.lang.Object
)的正确版本。这也可以让类装入器看到自己或父类或祖先装入的类,但是不能看到子女装入的类。
与其他类装入器不同,引导类装入器(也称作基本(primordial) 类装入器)不能由 Java 代码实例化。(通常是因为它是作为 VM 本身的一部分实现的。)这个类装入器可以从启动的类路径装入核心系统类,通常是位于 jre/lib 目录的 JAR 文件。但是能用
-Xbootclasspath
命令行选项修改这个类路径(稍后介绍)。
扩展(extension) 类装入器(也称作标准扩展 类装入器)是引导类装入器的一个孩子。它的主要职责是从扩展目录装入类,通常位于 jre/lib/ext 目录。这提供了简单地访问新扩展的能力,例如不同的安全扩展,不需要修改用户的类路径即可实现。
系统(system) 类装入器(也称作应用程序 类装入器)负责从
CLASSPATH
环境变量指定的路径装入代码。默认情况下,这个类装入器是用户创建的任何类装入器的父类。这也是
ClassLoader.getSystemClassLoader()
方法返回的类装入器。
下表显示了三个类装入器各自的类路径:
命令行选项
解释
涉及的类装入器
-Xbootclasspath:<用 ; 或 : 分隔的目录和 zip/JAR 文件> |
设置引导类和资源的搜索路径。 |
引导 |
-Xbootclasspath/a:<用 ; 或 : 分隔的目录和 zip/JAR 文件> |
把路径添加到启动类路径的末尾。 |
引导 |
-Xbootclasspath/p:<用 ; 或 : 分隔的目录和 zip/JAR 文件> |
把路径添加到启动类路径的前面。 |
引导 |
-Dibm.jvm.bootclasspath=<用 ; 或 : 分隔的目录和 zip/JAR 文件> |
这个属性的值被用作额外的搜索路径,它被插到 -Xbootclasspath/p: -Xbootclasspath: 选项定义的值。 定义的值和启动类路径之间。启动类路径或者是默认值,或者是 |
引导 |
-Djava.ext.dirs=<用 ; 或 : 分隔的目录和 zip/JAR 文件> |
指定扩展类和资源的搜索路径。 |
扩展 |
-cp or -classpath <用 ; 或 : 分隔的目录和 zip/JAR 文件> |
设置应用程序类和资源的搜索路径。 |
系统 |
-Djava.class.path=<用 ; 或 : 分隔的目录和 zip/JAR 文件> |
设置应用程序类和资源的搜索路径。 |
系统 |
三 JVM参数
在JVM中有一些参数提供了对classloader的调试信息。我们可以根据这些信息,对类装入过程中重要信息有所了解,利于我们分析,研究类的装入过程。在实际工作中,我只遇到过Linkageerror这么一个有关的错误例子,为了分析这个错误,我使用了-verbose的JVM参数。在启动JVM时加上这个参数,会是虚拟机在程序运行时,产生巨多类加载的信息,我们可以从中了解类是从哪里被加载到JVM里的。如果想要得到更详细的信息,可以设置-verbose:class参数。至于其它更多的相关参数或者IBM提供的JVM中特有的参数,我没具体使用过就不多说了,可以去看本文第一篇参考文献系列里的后续篇节。
四 相关异常
本来还想写一些相关例子,哈哈,想来想去,发现也没有第一篇参考文献系列里的例子好,所以就不写了,大家要看可以去IBM网站上看。http://www.ibm.com/developerworks/cn/java/j-dclp2.html
这里简单介绍一下相关的异常或错误,包括
ClassNotFoundException,
NoClassDefFoundError
,ClassCastException
,UnsatisfiedLinkError,
ClassCircularityError,
ClassFormatError,
ExceptionInInitializer。前三个大家会经常碰到,后四个可能大家不太容易见到。
ExceptionInInitializer
:如果初始化器突然完成,抛出一些异常 E
,而且 E
的类不是 Error
或者它的某个子类,那么就会创建 ExceptionInInitializerError
类的一个新实例,并用 E
作为参数,用这个实例代替 E
。如果 Java 虚拟机试图创建类 ExceptionInInitializerError
的新实例,但是因为出现 Out-Of-Memory-Error
而无法创建新实例,那么就抛出 OutOfMemoryError
对象作为代替。
ClassFormatError
:负责指定所请求的编译类或接口的二进制数据形式有误。这个异常是在类装入的链接阶段的校验过程中抛出。如果字节码发生了更改,例如主版本号或次版本号发生了更改,那么二进制数据的形式就会有误。例如,如果对字节码故意做了更改,或者在通过网络传送类文件时现出了错误,那么就可能发生这个异常。修复这个问题的惟一方法就是获得字节码的正确副本,可能需要重新进行编译。
ClassCircularityError
:类或接口由于是自己的超类或超接口而不能被装入。其实就是循环继承。A->B,B->A。
UnsatisfiedLinkError
:就是说在寻找本地方法时,在类路径中没有找到需要的定义。
ClassCastException:太常见了,不说了。
ClassNotFoundException,
NoClassDefFoundError:同样常见。这里说一下他们的区别。前者是被显式加载时,在类路径中找不到需要的类抛出的异常,后者是在隐式加载时,在类路径中找不到需要的类抛出的异常。显式,隐式的定义前面有介绍。
四 注意事项:
使用类装载器装载类的时候,类装载器会假设不以/结尾的路径指向的是JAR文件;而以/结尾的路径指向的是目录。
类装载器的双亲委托模式,每个类装载器的可视范围只包括本身和其双亲以及祖先。也就是说它看不到它的子装载器装载的类。
重载loadclass()方法,来实现自己的类装载器,以期采用非双亲委托模式的装载方式时,需要注意:程序中所有的类都必须在这个装载器的类路径中,包括java.lang.Object。
分享到:
相关推荐
Java ClassLoader学习总结 Java 类加载机制是 Java 中一个非常重要的机制,它负责加载 Class 文件到 JVM,以供程序使用。ClassLoader 是 Java 中的一个抽象类,它的主要作用是加载 Class 文件到 JVM 中。...
总结来说,对ClassLoader的理解对于Java开发者来说至关重要,它关乎程序的运行效率、安全性和可扩展性。通过深入学习和实践,我们可以更好地掌握Java平台的动态加载机制,提升软件设计和实现的能力。
总结来说,Java ClassLoader的定制是一项强大的技术,它可以让我们实现动态加载代码、构建灵活的插件系统等功能。然而,这也需要开发者对Java的内存管理和类加载机制有深入的理解,以便正确且安全地使用。通过研究...
### ClassLoader概述 在Java中,`ClassLoader`是负责加载类到JVM的重要组件之一。它不仅能够确保每个被加载的类都具有唯一的标识符,还能处理...理解类加载器的工作原理对于深入学习Java虚拟机和其底层机制非常重要。
Java中ClassLoader类加载学习总结 ClassLoader类加载是Java语言的一种创新,目的是为了将类的加载过程与虚拟机解耦,达到”通过类的全限定名来获取描述此类的二进制字节流“的目的。类加载器的基本模型就是双亲委派...
#### 六、总结 通过学习这篇教程,读者不仅可以了解到类加载器的基本概念及其在Java运行时系统中的作用,还能够掌握如何构建自定义的类加载器。这对于那些希望深入理解Java内部机制或者需要实现特定功能(例如动态...
通过本教程的学习,你将能够理解Java ClassLoader的基本概念及其工作原理,并学会如何创建自定义的ClassLoader。自定义ClassLoader不仅能够扩展JVM的功能,还能够在实际项目中解决特定问题,如动态加载远程资源、...
* 学习如何将自定义类加载器集成到Java应用中。 * 修改类加载器以适应Java 2版本。 为了更好地理解本教程,你需要具备以下基础: * 基本的Java编程能力,包括能够创建、编译和执行简单的命令行Java程序。 * 对类...
通过本文的学习,我们不仅深入了解了Java类加载器的工作原理,还通过具体的自定义类加载器实例掌握了如何在实践中应用这些理论知识。类加载器在Java开发中扮演着极其重要的角色,尤其是在需要实现动态加载类的应用...
总结来说,XWiki Commons Classloader Protocol Jar 5.4是XWiki项目的关键组件,负责类加载管理,而LaZyWorker是一个实现延迟任务和智能网络操作的工具。这两个项目都体现了开源社区的创新精神和对软件性能优化的...
总结来说,Tomcat 5.0.18的ClassLoader设计充分考虑了灵活性和安全性,通过定制的加载策略和资源处理机制,为Java Web应用提供了可靠的运行环境。深入研究其源码,可以提升开发者对Java虚拟机和Web容器工作原理的...
在这篇文章中,作者分享了自己在实习期间的经历和心得,包括了对 Java 虚拟机的学习和了解,特别是 ClassLoader 机制的学习和应用。 JAVA 虚拟机 Java 虚拟机(Java Virtual Machine,JVM)是 Java 程序的运行环境...
总结一下,类加载器是Java运行时环境的关键组成部分,它负责查找和加载类的字节码。通过自定义类加载器,我们可以控制类的加载顺序和来源,实现特定的功能,比如动态加载、模块隔离等。理解类加载器的工作机制对于...
总结来说,Java的ClassLoader机制是Java平台的重要组成部分,理解它的运作方式对于深入学习Java和进行JVM相关的优化至关重要。无论是默认的三个类加载器,还是自定义类加载器,它们共同确保了Java程序的正常运行和...
类加载器主要分为三类:Bootstrap ClassLoader(启动类加载器)、Extension ClassLoader(扩展类加载器)和Application ClassLoader(应用类加载器)。 1. Bootstrap ClassLoader(启动类加载器):它是用C++实现的...
理解类加载器的工作原理对于深入学习Java编程至关重要。在Java中,类加载器按照层次结构进行组织,包括引导类加载器、扩展类加载器和应用程序类加载器,每个都有特定的职责。 1. **引导类加载器(Bootstrap ...
同时,持续反思和总结是成长的关键,每周回顾自己的学习进度和遇到的问题,有助于及时调整学习策略,更好地适应实习岗位。 总的来说,作为Java工程师,熟悉JVM的工作原理和ClassLoader机制是基础,而能够灵活运用...
总结起来,Java的类加载机制保证了程序的稳定运行,而ASM库则提供了对字节码的直接操作能力,使得我们能够在运行时动态地修改类的行为。掌握这两者,开发者可以更好地理解和定制Java应用的运行过程,提升程序的灵活...
《Tomcat学习与分析总结资料》是一份涵盖了Tomcat服务器核心知识的综合资源,适合对Java Web应用服务器感兴趣的开发者深入学习。Tomcat是Apache软件基金会的项目,是世界上最流行的开源Servlet容器,它实现了Java ...