java类加载器与双亲委派模型

时间:2022-12-29 09:28:43

java 类加载时使用双亲委派模型进行类加载

类的声明周期:
java类加载器与双亲委派模型
加载:”加载”是”类加载”过程的一个阶段,此阶段完成的功能是:
  通过类的全限定名来获取定义此类的二进制字节流
  将此二进制字节流所代表的静态存储结构转化成方法区的运行时数据结构
  在内存中生成代表此类的java.lang.Class对象,作为该类访问入口.

验证:连接阶段第一步.验证的目的是确保Class文件的字节流中信息符合虚拟机的要求,不会危害虚拟机安全,使得虚拟机免受恶意代码的攻击.大致完成以下四个校验动作:
  文件格式验证
  源数据验证
  字节码验证
  符号引用验证

准备:连接阶段第二步,正式为类变量分配内存并设置变量的初始值.(仅包含类变量,不包含实例变量).  

解析:连接阶段第三步,虚拟机将常量池中的符号引用替换为直接引用,解析动作主要针对类或接口,字段,类方法,方法类型等等..

初始化:类的初始化是类加载过程的最后一步,在该阶段,才真正意义上的开始执行类中定义的java程序代码.该阶段会执行类构造器.

使用:使用该类所提供的功能.

卸载:从内存中释放.

java 类加载器
对于 JVM 来说类加载器可以分为两类:启动类加载器(Bootstrap ClassLoader)和其他类加载器,启动类加载器负责加载 java 核心类库 lib/rt.jar 使用 C++编写,是虚拟机自身的一部分,其他类加载器,负责加载其他类,使用 java编写,继承自 ClassLoader
java系统中提供的主要三种类加载器:

启动类加载器(Bootstrap ClassLoader):
  这个类加载器负责将\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,此类加载器并不继承于java.lang.ClassLoader,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分.

扩展类加载器(Extendsion ClassLoader):
  这个类加载器负责加载\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器. ExtClassLoader extends URLClassLoader

应用程序类加载器(Application ClassLoader):
这个类加载器负责加载用户类路径(CLASSPATH)下的类库,一般我们编写的java类都是由这个类加载器加载,这个类加载器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也称为系统类加载器.一般情况下这就是系统默认的类加载器. AppClassLoader extends URLClassLoader

除此之外,我们还可以加入自己定义的类加载器,以满足特殊的需求,需要继承java.lang.ClassLoader类.

类加载器层次结构图
java类加载器与双亲委派模型
注意图片中的类加载器的上下层级关系只是逻辑关系,而不是指类的继承结构关系,实际上,扩展类加载器与系统类加载器都继承自URLClassLoader,但两者没有任何继承关系
代码测试一下一般类的类加载器:

public class TestClassLoader
{
public static void main(String[] args)
{
System.out.println(TestClassLoader.class.getClassLoader());
System.out.println(TestClassLoader.class.getClassLoader().getParent());
System.out.println(TestClassLoader.class.getClassLoader().getParent().getParent());
}
}

输出:

sun.misc.Launcher$AppClassLoader@2a139a55
sun.misc.Launcher$ExtClassLoader@7852e922
null

可见类TestClassLoader的加载器为AppClassLoader,其父加载器为ExtClassLoader,而ExtClassLoader的父加载器为BootstrapClassLoader,其打印出来为 null

双亲委派模型
双亲委派模型是一种组织类加载器之间关系的一种规范,他的工作原理是:如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载.
这样的好处是:java类随着它的类加载器一起具备了带有优先级的层次关系.这是十分必要的,比如java.langObject,它存放在\jre\lib\rt.jar中,它是所有java类的父类,因此无论哪个类加载都要加载这个类,最终所有的加载请求都汇总到顶层的启动类加载器中,因此Object类会由启动类加载器来加载,所以加载的都是同一个类,如果不使用双亲委派模型,由各个类加载器自行去加载的话,系统中就会出现不止一个Object类,应用程序就会全乱了.

Class.forname()与ClassLoader.loadClass():
Class.forname(),静态方法,根据传入的类的全限定名返回一个Class对象.该方法在将Class文件加载到内存的同时,会执行类的初始化.
ClassLoader.loadClass():这是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化.该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器.

逆双亲委派模型
双亲委派模型并不能满足所有使用条件,在以下条件时就会打破双亲委派模型
除了 jdk1.2之前由于没有采用双亲委派模型造成的原因,现在破坏双亲委派主要由以下两种原因造成:
spi 服务时(例如 jdbc 驱动的加载),上层类加载器需要调用下层类加载器加载的类,此时需要用到线程上下文类加载器,使用Thread.currentThread().setContextClassLoader();可以设置当前线程的上下文加载器,如果创建线程时还未设置,它将从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那它就是默认的应用程序类加载器,使用这个类加载器,父类加载器可以请求子类加载器去完成类加载的动作
osgi环境下,由于需要实现热拔插功能,需要实现类的动态加载与卸载,更换 Bundle 时,需要将 Bundle 连通类加载器一起换掉以实现代码的热替换

实现自己的类加载器
只需要继承 ClassLoader 或者继承任意继承了 ClassLoader 的类即可
可以观察 ClassLoader 加载的核心代码 loadClass

protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);

// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}

可以看到,loadClass先查找该类是否被加载,如果没有被加载则尝试使用父类加载器加载,如果都不行最后才选择自己加载
为了不破坏双休委派模型,建议自定义类加载器时只覆盖findLoadedClass方法,而不去覆盖 loadClass,如果需要覆盖 loadClass,请注意保留双亲委派机制(特殊需求的类加载器除外)