类装载器
Java虚拟机使用每一个类的第一件事情就是将该类的字节码装载近来,装载类字节码的功能是由类装载器完成的,类装载器负责根据一个类的名称来定位和生成类的字节码数据后返回给Java虚拟机。最常见的类装载器是将要加载的类名转换成一个.class文件名,然后从文件系统中找到该文件并读取其中的内容,这种类装载器也不是直接将.class文件中的内容原封不动地返回给Java虚拟机,它需要将.class文件中的内容转换成Java虚拟机使用的类字节码。不管类装载器采用什么方式,只要能够在内存中制造出给Java虚拟机调用类字节码即可,所以把类装载器描述为类字节码的制造器更容易让人理解。
当一个类被加载后,Java虚拟机将其编译为可执行代码存储在内存中,并将索引信息存储进一个HashTable中,其所关键字就是这个类的完整名称。当Java虚拟机需要用到某个类时,它先使用类名作为关键字在HashTable中查找相应的信息,如果该可执行代码已经存在,Java虚拟机直接从内存里调用该可执行代码,反之则调用类装载器加载并进行加载和编译。
一个Java类用来描述现实生活中有形或无形的事物,甚至可以是一种思想,一种概念。不可否认,Java程序中的类本身也是一种事物,它也可以用一个Java类描述,这个特殊的类名就叫Class。Class类用于描述Java程序语言中使用的一个类的有关信息,它里面定义了对其所描述的类进行各种操作方法。可以认为,类装载器装载某个类的字节码的过程实际上就是在创建Class类的一个实例对象,这个Class类的实例对象封装的内容正好是当前加载的类的字节码数据,也就是Java虚拟机对当前加载类编译后存储在内存中的可执行代码。
类装载器本身也是一个Java类,Java类库中提供了一个java.lang.ClassLoader来作为类装载器的基类,Java虚拟机和程序都调用ClassLoader类的loadClass方法来加载类,ClassLoader是一个抽象类,真正的类装载器必须是ClassLoader的子类。Class类中定义了一个getClassLoader方法,用于返回它所描述的类的类装载器对象,这个返回对象的类型就是ClassLoader。
在一个Java虚拟机中可以有多个类装载器,当Java虚拟机要装载一个类时,它通过以下一些方式来选择类装载器:
1) 一个类装载器本身也是一个Java类,所以类装载器自身也需要被另外一个类装载器装载。Java虚拟机中内嵌了一个称为Bootstrap的类装载器,它是用特定于操作系统的本地代码实现的,属于Java虚拟机的内核,这个Bootstrap类装载器不用专门的类装载器去进行装载。Bootstrap类装载器负责加载Java核心包中的类,这些类的Class.getClassLoader方法返回值为null,即表示是Bootstrap类装载器。Java核心包中有另外两个类装载器,ExtClassLoader和AppClassLoader,它们都是Java语言编写的Java类,其中ExtClassLoader类装载器负责加载存放在<JAVA_HOME>/jre/lib/ext目录下的Jar包中的类,AppClassLoader负责加载应用程序的启动执行类,即当使用Java命令去执行一个类时,Java虚拟机使用AppClassLoader加载这个类。在编译和运行Java程序时,都会通过ExtClassLoader类装载器去<JDK安装主目录>\jre\lib\ext目录下的Jar包中搜索要加载的类,所以如果将包含Servlet API的jar文件复制到该目录下,在编译Servlet程序时,就不必在CLASSPATH环境变量中增加包含Servlet API的jar文件。
2) 一个Java虚拟机中的所有类装载器采用具有父子关系的树行结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象。如果没有指定的话,则以ClassLoader.getSystemClassLoader()方法返回的系统类装载器作为其父级类装载器对象。系统类装载器通常被设置为启动应用程序的AppClassLoader,它是在getSystemClassLoader()方法第一次被调用时设置的,而getSystemClassLoader()方法的第一次调用发生在应用程序启动的早期阶段,可以通过java.system.class.loader系统属性来将系统类装载器设置为其他类装载器。ExtClassLoader是AppClassLoader的父级类装载器,ExtClassLoader没有父级类装载器。
3) 如果类A中使用new 关键字创建类B,Java虚拟机将使用加载类A的类装载器来加载类B。如果一个类中调用Class.forName方法来动态加载另外一个类,可以通过传递给Class.forName方法的一个参数来指定另外那个类的类装载器,如果没有指定该参数,则使用加载当前类的类装载器。依据一个类的存放位置,这个类最终只能由一个特定的类装载器装载。对于一个已被父类类装载器装载的类来说,Java虚拟机默认也使用这个父级类装载器去装载它所调用的其他类,由于父级类装载器不回委托子级类装载器去装载类,所以,在一般情况下,一个已被父级类装载器装载的类无法调用那些只能被子级类装载器发现和装载的其他类。
4) 每个运行中的线程都有一个关联的上下文类装载器,可以使用Thread.setContextClassLoader()方法设置线程的上下文类装载器。每个线程默认的上下文类装载器是其父线程的上下文类装载器,而主线程的类装载器初始被设置为ClassLoader.getSystemClassLoader()方法返回的系统类装载器。