类加载器和类加载机制

文章目录

  • 类加载过程(生命周期)
    • 加载
    • 校验
    • 准备
    • 解析
    • 初始化
  • 类加载器
    • 类加载器作用
    • 类加载器分类
  • 双亲委派模型
    • 双亲委派模式的实现

类加载过程(生命周期)

JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。其中加载、检验、准备、初始化和卸载这个五个阶段的顺序是固定的,而解析则未必。为了支持动态绑定,解析这个过程可以发生在初始化阶段之后。
类加载器和类加载机制_第1张图片

加载

由于Class文件并不一定由java源码编译而来,从其他途径产生的Class文件在字节码语言层面上,语义也是可以表达出来的。但是可能会载入有害字节流导致系统崩溃,如果不检查会对虚拟机造成损伤。

加载过程主要完成三件事情

  1. 通过类的全限定名来获取定义此类的二进制字节流
  2. 将这个类字节流代表的静态存储结构转为方法区的运行时数据结构
  3. 在堆中生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口。

这个过程主要就是类加载器完成。

校验

此阶段主要确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全。

  1. 文件格式验证:基于字节流验证。验证字节流是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。
  2. 元数据验证:基于方法区的存储结构验证。对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。
  3. 字节码验证:基于方法区的存储结构验证。通过数据流和控制流分析,确定程序语义是合法的符合逻辑的。如果一个类的字节码没有通过字节码验证,那一定是有问题的;但通过了也不能说明没有问题。
  4. 符号引用验证:基于方法区的存储结构验证。对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。

准备

为类变量分配内存,并将其初始化为默认值。(此时为默认值,在初始化的时候才会给变量赋值)即在方法区中分配这些变量所使用的内存空间。例如:

public static int value = 123;

此时在准备阶段过后的初始值为0而不是123;将value赋值为123的putstatic指令是程序被编译后,存放于类构造器方法之中.特例:

public static final int value = 123;

此时value的值在准备阶段过后就是123。

解析

把常量池内的符号引用转换为直接引用。

  • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
  • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

主要有以下四种:

  1. 类或接口的解析
  2. 字段解析
  3. 类方法解析
  4. 接口方法解析

初始化

初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。

java中,对于初始化阶段,有且只有以下五种情况才会对要求类立刻“初始化”(加载,验证,准备,自然需要在此之前开始):

  1. 使用new关键字实例化对象、访问或者设置一个类的静态字段(被final修饰、编译器优化时已经放入常量池的例外)、调用类方法,都会初始化该静态字段或者静态方法所在的类。
  2. 初始化类的时候,如果其父类没有被初始化过,则要先触发其父类初始化。
  3. 使用java.lang.reflect包的方法进行反射调用的时候,如果类没有被初始化,则要先初始化。
  4. 虚拟机启动时,用户会先初始化要执行的主类(含有main)
  5. jdk 1.7后,如果java.lang.invoke.MethodHandle的实例最后对应的解析结果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,并且这个方法所在类没有初始化,则先初始化。

类加载器

类加载器作用

  • 对于任意一个类,必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。也就是说,比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类必定不相等。
  • 这里的相等包括代表类的Class 对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。

类加载器分类

  1. 启动类加载器(Bootstrap Class Loader):这个类加载器负责加载存放在\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。启动类加载器无法被Java程序直接引用,但可间接引用。在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理,直接使用null代替即可。
  2. 扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。它负责加载\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
  3. 应用程序类加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现。由于应用程序类加载器是ClassLoader类中的getSystem-ClassLoader()方法的返回值,所以有些场合中也称它为“系统类加载器”。它负责加载用户类路径(ClassPath)上所有的类库,在程序中可以直接使用,为程序中的默认加载器。

双亲委派模型

类加载器和类加载机制_第2张图片

上图中类加载器之间的层次关系称为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当有自己的父类加载器。

*为什么要提出双亲委派模型?

其实就是为了让基础类得以正确地统一地加载。
从上面的图可以看出,如果你也定义了一个 java.lang.Object类,通过双亲委派模式是会把这个请求委托给启动类加载器,它扫描\lib目录就找到了 jdk 定义的 java.lang.Object 类来加载,所以压根不会加载你写的 java.lang.Object类,这就可以避免一些程序不小心或者有意的覆盖基础类

所以它的优势可以归结为:
1.确保类只被加载一次,避免重复加载
2.避免核心类被任意或恶意篡改

类加载器和类加载机制_第3张图片

工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都如此,因此所有的加载请求应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时即它的搜索范围内没有找到所需要的类,子加载器才会尝试自己去加载。

双亲委派模式的实现

双亲委派模型的代码实现集中在java.lang.ClassLoader的loadClass()方法当中。

  • 首先检查类是否被加载,没有则调用父类加载器的loadClass()方法;
  • 若父类加载器为空,则默认使用启动类加载器作为父加载器;
  • 若父类加载失败,抛出ClassNotFoundException 异常后,再调用自己的findClass() 方法。

loadClass源代码如下:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    //1.首先检查类是否被加载
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
               //2.没有则调用父类加载器的loadClass()方法;
                c = parent.loadClass(name, false);
            } else {
               //3.若父类加载器为空,则默认使用启动类加载器作为父加载器;
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            //4.若父类加载失败,抛出ClassNotFoundException 异常后再调用自己的findClass() 方法。
            c = findClass(name);
        }
    }
    if (resolve) {
    	// 链接指定的类
        resolveClass(c);
    }
    return c;
}

你可能感兴趣的:(java啊啊啊啊,java,类)