Java类加载器和双亲委派模型.md

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014453515/article/details/84976920

0.类加载过程

一般来说,类加载分为3个过程,加载,链接和初始化。
1.加载阶段,是Java将字节码数据从不同数据源读取到JVM中,并映射为JVM认可的Class对象,这里的数据源可能有Jar包,class文件,甚至网络数据源等。如果输入数据不是ClassFile结构,则会抛出ClassFormatError。

2.链接,这是核心步骤,将原始的类定义信息平滑过渡到JVM运行过程中。细分为3个步骤

  • 验证 JVM需要验证字节信息是否符合Java虚拟机规范,
  • 准备 创建类或接口中的静态变量,并初始化静态变量的初始值。这里的初始化和第3步的初始化时有明显区别的,侧重点在于分配所需要的内存空间,不回去执行更近一部的JVM指令。
  • 解析 这一步会将常量池中的符号引用替换为直接引用。

3.初始化(显示初始化)
这一步真正执行类初始化的代码逻辑,包括类静态字段和类定义静态代码块的初始化动作(需要额外调用putstatic等JVM指令),编译器在编译阶段就会把这部分逻辑给整理好。注意,父类的初始化逻辑要先于子类执行。

补充一个 类和实例的初始化执行优先级

初始化过程:

  1. 初始化父类中的静态成员变量和静态代码块 ;
  2. 初始化子类中的静态成员变量和静态代码块 ;
  3. 初始化父类的普通成员变量和代码块,再执行父类的构造方法;
  4. 初始化子类的普通成员变量和代码块,再执行子类的构造方法;

注意:静态块(包括静态成员变量和静态代码块)在JVM中只初始化一次。

1.双亲委派模型的定义

当类加载器视图加载某一个类型的时候,除非父加载器找不到当前类型,否则尽量将当前任务代理给父加载器去做。使用委派模型的目的是,避免重复加载Java类型。

#1.JVM 提供的3个默认的类加载器

1.1.BootStrap ClassLoader

称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等,是通过查找sun.boot.class.path这个系统属性所得知的。它是一个超级公民,即便是开启了Security Manager之后,JDK依然付给他了所有的加载权限。

1.2 Extension ClassLoader

称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。这就是所有的extendsion机制,这个目录也可以通过设置“java.ext.dirs”来覆盖。

比如在运行jar是,通过这个参数指定它所依赖的jar:

java -jar xx.jar -Djava.ext.dirs=./lib/ com.Main

1.3 App ClassLoader

称为应用类加载器,负责加载应用程序classpath目录下的所有jar和class文件。我们听到的“系统类加载器”,通常就是指这个。你可以自定义自己的类加载器,使用如下参数替换系统类加载器:

java -Djava.system.class.loader=com.MyClassLoader Main

三个类加载器的关系图:
在这里插入图片描述

2.类加载器的特征

2.1 双亲委派模型

绝大多数类加载器都遵循双亲委派模型,但不是所有的。有时候,启动类加载器加载的类型,可能需要加载用户代码,用户可以在标准API框架下,提供自己的实现。

2.2 可见性

子类加载器可以访问父类加载器加载过的类型,反过来是不允许的。

2.3 单一性

子类加载器可以访问父类加载器加载过的类型,所以父类加载器加载过的类型不会在子类加载器中重复加载。但是同级的类加载器是互不可见的,同一类型可以被多次加载,但是加载出的类型时不同的class。

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。比如网络上的一个Java类org.classloader.simple.NetClassLoaderSimple,javac编译之后生成字节码文件NetClassLoaderSimple.class,ClassLoaderA和ClassLoaderB这两个类加载器并读取了NetClassLoaderSimple.class文件,并分别定义出了java.lang.Class实例来表示这个类,对于JVM来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个Class实例生成具体的对象进行转换时,就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型

3.自定义类加载器的应用场景

3.1 实现进程内隔离

类加载器实际上用作不同的命名空间,以提供类似容器,模块化的效果。例如,两个模块依赖不同版本的类库,如果被不同的类加载器加载,就可以互不干扰。

3.2 应用需要从不同的数据源获取类定义

应用需要从不同的数据源获取类定义信息,如网络数据源,而不是本地文件系统。

3.3 动态生成或修改类型

这个过程需要自己操作字节码

自定义类加载器的加载过程
1.通过制定名称,找到其二进制实现,往往自定义加载器就在这里实现“定制”部分。
2.创建Class,完成类加载过程。从二进制到Class的转化,不需要我们实现,通常依赖DefineClass

猜你喜欢

转载自blog.csdn.net/u014453515/article/details/84976920