在做Java开发时了解Java类加载机制是非常好的。而对类加载机制的基本理解对Java开发人员处理类加载器(ClassLoader)相关的异常也很有帮助。
类加载器委托机制
Java类的装载是通过类加载器(CL)来完成的,这些类加载器负责将类加载到JVM中。简单的应用可以使用java平台自带的类加载器来加载自身的类,而稍微复杂一些的应用则倾向于自定义类加载来加载自身的类。
在java中类加载器是以树状结构组织的。通过请求一个类加载器并通过在其缓存中查找来确定某个类是否已被加载。如果在缓存中已经有该类,那么CL直接返回;否则,该类加载器委托其父类加载器来加载该类。如果这个类加载器没有父类或者父类不能加载这个类则会抛出ClassNotFoundException异常,此时被请求的类加载器会尝试去在它自身的路径下来查找并加载需要被加载类的类文件。如果找到了这个类,就将其返回;否则会抛出ClassNotFoundException异常。其中缓存的查找方式为递归的从子类到父类查找,直到到达树(译注:类加载器已树状结构组织)的根节点或者在缓存中找到需要的类为止。如果缓存搜索已到达根节点,则类加载器会从父类到子类再次展开递归来加载需要的类。总的来说顺序如下:
1. 缓存
2. 父类
3. 子类
这种机制确保类会类会被最接近根结点的类加载器加载。请谨记,父类加载器总是有优先加载类的权限。而这很重要的作用就是确保核心java类都被bootstrap类加载器加载,这个类加载器是用来保证正确版本的类加载,如java.lang.Object。此外,bootstrap类加载器还保证一个类加载只能看到自身或其父类(或父类的父类等)加载的类,而不能看到其子类以及兄弟类(译注:继承同一个父类的类称为兄弟类)所加载的类。
下图阐明了类加载器的层级关系。根加载器是bootstrap类加载,该加载器是通过native方法实现,并且不能在java代码中实例化。
BootStrap之后便是扩展类加载器,该加载器的主要责任是从扩展目录中加载类并且在新的JVM中不修改用户classpath的情况下来提供简单访问JVM扩展的能力。而系统或者应用类加载器则负责从环境变量CLASSPATH中指定的路径来加载类;这种类加载器可以通过调用ClassLoader.getSystemClassLoader()方法获得。
类加载阶段
具体的类加载可以分为3个阶段:物理加载,链接,初始化。
1 在物理加载阶段,要求类可以在指定的classpath路径中被找到。如果可以找到类文件,通过读取类文件来加载字节码。这个处理过程给出了单个类对象的一个基本内存结构,但是像方法、字段以及对以其他类的一些引用这些概念在这个阶段是不知道的。
2 链接阶段可以拆分为3个主要阶段,因为这个阶段比较复杂:
2.1 通过类加再起校验字节码,该过程会在字节码上执行一系列校验。
2.2 类准备。这个阶段准备必要的数据结构,表示类中所定义的字段、方法以及所实现的接口。
2.3 解析某个特定类中所有的其他类引用。类可以通过很多途径被应用,具体如下
2.3.1 父类
2.3.2 接口
2.3.3 字段类型
2.3.4 方法返回值类型
2.3.5 在方法中使用的本地变量类型
3 在初始化阶段,这个类中包含的所有初始化块都会被执行,一遍静态变量都会被初始化为默认值。
有趣的是类加载可以通过延迟加载机制来实现,因此一部分只是在第一次使用的时候才被加载而不是一开始就加载。
异常
在处理类加载问题时最大的挑战在于问题很难在类加载这个处理过程体现出来,而是在之后使用这个类的时候才会被体现出来。以下是类加载时两个相关的异常以及造成对应异常的可能原因:
ClassNotFoundException
l 当被加载的类相关文件、目录或者其他资源没有添加到对应了类加载器或者父类加载器时
l 当类加载器的父类设置错误时
l 当类加载器使用错误时
NoClassDefFoundError
l 当被加载的类相关文件、目录或者其他资源没有添加到对应了类加载器或者父类加载器时
l 当类加载器的父类设置错误时
l 当一个类被加载时,该类中所包含的引用对该类的类加载器不可达时,例如加载器的子类
1. 本文由程序员学架构翻译
2. 本文译自 http://www.techjava.de/topics/2008/01/java-class-loading/
3. 转载请务必注明本文出自:程序员学架构(微信号:archleaner )
4. 更多文章请扫码: