类加载器浅谈

来源:互联网 发布:日工作计划软件 编辑:程序博客网 时间:2024/05/19 02:17

  1、基本概念

        顾名思义,类加载器(class loader)用来加载java类到java虚拟机中。一般来说,java虚拟机使用java类的方式如下:java源程序(.java文件)在经过java编译器编译之后就被转换成java字节代码(.class文件)。类加载器负责读取java字节代码,并转换成java.lang.Class类的一个实例。每个这样的实力用来表示一个java类。通过此实例的newInstance()方法就可以创建一个该类的对象。实际的情况可能更复杂,比如java字节代码克鞥是通过工具动态自动生成的,也可能是通过网络下载的。基本上,所有的类加载器都是java.lang.ClassLoader类的一个实例。

  2、java.lang.ClassLoader类介绍

        java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成器对应的字节码,然后从这些字节代码中定义出一个java类,即java.lang.Class类的一个实例。初次之外,ClassLoader还负责加载java应用所需的资源,如图像文件和配置文件等。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如表一所示。

表1.ClassLoader中与加载类相关的方法方法说明getParent ()返回该类加载器的父类加载器loadClass(String name)加载名称为name的类,返回的结果是java.lang.Class类的实例findClass(String name)查找名称为name的类,返回的结果是java.lang.Class类的实例findLoadedClass(String name)查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例defineClass(String name, byte[] b, int off, int len)把字节数组b中的内容转换成java类,返回的结果是java.lang.Class类的实例。这个方法被声明为final类型。resolveClass(Class<?> c)链接指定的java类


对于表1中给出的方法,表示类名称的name参数的值是类的binary name(二进制名称)。需要注意的是内部类的表示,如com.example.Good表示方式。

  3、类加载器的树状组织结构

        java中的类加载器大致可以分成两类,一类是系统提供的,另一类则是由java应用开发人员编写的。系统提供的类加载主要有下面三个:

       引导类加载器(bootstrap class loader):它用来加载java的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader.

       扩展类加载器(extension class loader):它用来加载java的扩展库。java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载java类。

       系统类加载器(system class loader):它根据java应用的类路径(CLASSPATH)来加载java类。一般来说,java应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。除了系统提供的类加载器意外,开发人员通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊需求。

       除了引导类加载器之外,所有的类加载器都有一个加载器。通过表1中给出的getParent()方法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加载器来说,其父类是系统类加载器。

       图1类加载器树状组织结构示意图

   

  5、类加载器的代理模式

        类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依此类推。在介绍代理模式背后的动机之前,首先需要说明一下java虚拟机是如何判断两个java类是相同的。java虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,别不同的类加载器加载之后所得到的类,也是不同的。比如一个java类come.example.Sample,编译之后生成了字节代码文件Sample.class。两个不同类加载器ClassLoaderA和ClassLoaderB分别读取了这个Sample.class文件,并定义了两个java.lang.Class类的实例来表示这个类。这两个实例是不同的。对java虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常ClassCastException.

  6、加载类的过程

       在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载来加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类加载工作是通过调用defineClass来实现的;而启动类的加载过程是通过调用loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在java虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类com.example.Outer引用了类com.example.Inner,则由类com.example.Outer的定义加载器负责启动类com.example.Inner的加载过程。

  7、线程的上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

0 0
原创粉丝点击