Java虚拟机类载入过程是把Class类文件载入到内存。并对Class文件里的数据进行校验、转换解析和初始化,终于形成能够被虚拟机直接使用的java类型的过程。
在载入阶段,java虚拟机须要完毕下面3件事:
a.通过一个类的全限定名来获取定义此类的二进制字节流。
b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的执行时数据结构。
c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的訪问入口。
Java虚拟机的类载入是通过类载入器实现的, Java中的类载入器体系结构例如以下:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center">
(1).BootStrap ClassLoader:启动类载入器。负责载入存放在%JAVA_HOME%\lib文件夹中的,或者通被-Xbootclasspath參数所指定的路径中的。而且被java虚拟机识别的(仅依照文件名称识别。如rt.jar,名字不符合的类库,即使放在指定路径中也不会被载入)类库到虚拟机的内存中,启动类载入器无法被java程序直接引用。
(2).Extension ClassLoader:扩展类载入器,由sun.misc.Launcher$ExtClassLoader实现,负责载入%JAVA_HOME%\lib\ext文件夹中的。或者被java.ext.dirs系统变量所指定的路径中的全部类库,开发人员能够直接使用扩展类载入器。
(3).Application ClassLoader:应用程序类载入器,由sun.misc.Launcher$AppClassLoader实现。负责载入用户类路径classpath上所指定的类库。是类载入器ClassLoader中的getSystemClassLoader()方法的返回值,开发人员能够直接使用应用程序类载入器,假设程序中没有自己定义过类载入器,该载入器就是程序中默认的类载入器。
注意:上述三个JDK提供的类载入器尽管是父子类载入器关系,可是没有使用继承,而是使用了组合关系。
从JDK1.2開始。java虚拟机规范推荐开发人员使用双亲委派模式(ParentsDelegation Model)进行类载入,其载入步骤例如以下:
(1).假设一个类载入器收到了类载入请求,它首先不会自己去尝试载入这个类,而是把类载入请求委派给父类载入器去完毕。
(2).每一层的类载入器都把类载入请求委派给父类载入器,直到全部的类载入请求都应该传递给顶层的启动类载入器。
(3).假设顶层的启动类载入器无法完毕载入请求,子类载入器尝试去载入,假设连最初发起类载入请求的类载入器也无法完毕载入请求时,将会抛出ClassNotFoundException。而不再调用其子类载入器去进行类载入。
双亲委派 模式的类载入机制的长处是java类它的类载入器一起具备了一种带优先级的层次关系。越是基础的类。越是被上层的类载入器进行载入,保证了java程序的稳定执行。双亲委派模式的实现:
- protected synchronized Class<?
> loadClass(String name, Boolean resolve) throws ClassNotFoundException{
- //首先检查请求的类是否已经被载入过
- Class c = findLoadedClass(name);
- if(c == null){
- try{
- if(parent != null){//委派父类载入器载入
- c = parent.loadClass(name, false);
- }
- else{//委派启动类载入器载入
- c = findBootstrapClassOrNull(name);
- }
- }catch(ClassNotFoundException e){
- //父类载入器无法完毕类载入请求
- }
- if(c == null){//本身类载入器进行类载入
- c = findClass(name);
- }
- }
- if(resolve){
- resolveClass(c);
- }
- return c;
- }
protected synchronized Class<? > loadClass(String name, Boolean resolve) throws ClassNotFoundException{
//首先检查请求的类是否已经被载入过
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){//委派父类载入器载入
c = parent.loadClass(name, false);
}
else{//委派启动类载入器载入
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//父类载入器无法完毕类载入请求
}
if(c == null){//本身类载入器进行类载入
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}
若要实现自己定义类载入器,仅仅须要继承java.lang.ClassLoader 类。而且重写其findClass()方法就可以。java.lang.ClassLoader 类的基本职责就是依据一个指定的类的名称,找到或者生成其相应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class 类的一个实例。除此之外,ClassLoader 还负责载入 Java 应用所需的资源,如图像文件和配置文件等。ClassLoader中与载入类相关的方法例如以下:
方法 |
说明 |
getParent() |
返回该类载入器的父类载入器。 |
loadClass(String name) |
载入名称为 二进制名称为name 的类。返回的结果是 java.lang.Class 类的实例。 |
findClass(String name) |
查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例。 |
findLoadedClass(String name) |
查找名称为 name 的已经被载入过的类。返回的结果是 java.lang.Class 类的实例。 |
resolveClass(Class<?> c) |
链接指定的 Java 类。 |
注意:在JDK1.2之前。类载入尚未引入双亲委派模式,因此实现自己定义类载入器时经常重写loadClass方法,提供双亲委派逻辑。从JDK1.2之后,双亲委派模式已经被引入到类载入体系中。自己定义类载入器时不须要在自己写双亲委派的逻辑,因此不鼓舞重写loadClass方法,而推荐重写findClass方法。
在Java中。随意一个类都须要由载入它的类载入器和这个类本身一同确定其在java虚拟机中的唯一性,即比較两个类是否相等。仅仅有在这两个类是由同一个类载入器载入的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,仅仅要载入它的类载入器不同样,那么这两个类必然不相等(这里的相等包含代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceofkeyword的结果)。
样例代码例如以下:
- package com.test;
- public class ClassLoaderTest {
- public static void main(String[] args)throws Exception{
- //匿名内部类实现自己定义类载入器
- ClassLoader myClassLoader = new ClassLoader(){
- protected Class<?> findClass(String name)throws ClassNotFoundException{
- //获取类文件名称
- String filename = name.substring(name.lastIndexOf(“.”) + 1) + “.class”;
- InputStream in = getClass().getResourceAsStream(filename);
- if(in == null){
- throw RuntimeException(“Could not found class file:” + filename);
- }
- byte[] b = new byte[in.available()];
- return defineClass(name, b, 0, b.length);
- }catch(IOException e){
- throw new ClassNotFoundException(name);
- }
- };
- Object obj = myClassLoader.loadClass(“com.test.ClassLoaderTest”).newInstance();
- System.out.println(obj.getClass());
- System.out.println(obj instanceof com.test. ClassLoaderTest);
- }
- }
package com.test; public class ClassLoaderTest {
public static void main(String[] args)throws Exception{
//匿名内部类实现自己定义类载入器
ClassLoader myClassLoader = new ClassLoader(){
protected Class<? > findClass(String name)throws ClassNotFoundException{
//获取类文件名称
String filename = name.substring(name.lastIndexOf(“.”) + 1) + “.class”;
InputStream in = getClass().getResourceAsStream(filename);
if(in == null){
throw RuntimeException(“Could not found class file:” + filename);
}
byte[] b = new byte[in.available()];
return defineClass(name, b, 0, b.length);
}catch(IOException e){
throw new ClassNotFoundException(name);
}
};
Object obj = myClassLoader.loadClass(“com.test.ClassLoaderTest”).newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.test. ClassLoaderTest);
}
}
输出结果例如以下:
com.test.ClassLoaderTest
false
之所以instanceof会返回false。是由于com.test.ClassLoaderTest类默认使用Application ClassLoader载入,而obj是通过自己定义类载入器载入的。类载入不同样,因此不相等。
类载入器双亲委派模型是从JDK1.2以后引入的。而且仅仅是一种推荐的模型。不是强制要求的,因此有一些没有遵循双亲委派模型的特例:
(1).在JDK1.2之前,自己定义类载入器都要覆盖loadClass方法去实现载入类的功能,JDK1.2引入双亲委派模型之后,loadClass方法用于委派父类载入器进行类载入。仅仅有父类载入器无法完毕类载入请求时才调用自己的findClass方法进行类载入,因此在JDK1.2之前的类载入的loadClass方法没有遵循双亲委派模型。因此在JDK1.2之后。自己定义类载入器不推荐覆盖loadClass方法。而仅仅须要覆盖findClass方法就可以。
(2).双亲委派模式非常好地攻克了各个类载入器的基础类统一问题,越基础的类由越上层的类载入器进行载入,可是这个基础类统一有一个不足,当基础类想要调用回下层的用户代码时无法委派子类载入器进行类载入。为了解决问题JDK引入了ThreadContext线程上下文,通过线程上下文的setContextClassLoader方法能够设置线程上下文类载入器。
JavaEE仅仅是一个规范,sun公司仅仅给出了接口规范,详细的实现由各个厂商进行实现,因此JNDI。JDBC,JAXB等这些第三方的实现库就能够被JDK的类库所调用。
线程上下文类载入器也没有遵循双亲委派模型。
(3).近年来的热码替换,模块热部署等应用要求不用重新启动java虚拟机就能够实现代码模块的即插即用。催生了OSGi技术。在OSGi中类载入器体系被发展为网状结构。OSGi也没有全然遵循双亲委派模型。