关于java:深入理解Java类加载机制

36次阅读

共计 3207 个字符,预计需要花费 9 分钟才能阅读完成。

你多学一样本事,就少说一句求人的话

一、类的生命周期

二、类的加载过程

加载

通过一个类的残缺门路查找此类字节码文件(class 文件即二进制文件)。将二进制文件的动态存储构造转化为办法区的运行时数据结构,并利用二进制流文件创建一个 Class 对象,存储在 Java 堆中用于对办法区的数据结构援用的入口;

  1. class 文件的起源:有一点须要留神的是类加载机制不仅能够从文件系统读取 class 文件,也能够通过网络获取,其余 jar 包或者其余程序生成,如 JSP 利用。
  2. 类加载器:讲到类加载不得不讲到类加载的程序和类加载器。Java 中大略有四品种加载器,别离是:启动类加载器(Bootstrap ClassLoader),扩大类加载器(Extension ClassLoader),零碎类加载器(System ClassLoader),自定义类加载器(Custom ClassLoader),顺次属于继承关系(留神这里的继承不是 Java 类外面的 extends)

  1. 启动类加载器(Bootstrap ClassLoader):次要负责加载寄存在 Java_Home/jre/lib 下,或被 -Xbootclasspath 参数指定的门路下的,并且能被虚拟机辨认的类库(如 rt.jar,所有的 java.* 结尾的类均被 Bootstrap ClassLoader 加载),启动类加载器是无奈被 Java 程序间接援用的。
  2. 扩大类加载器(Extension ClassLoader):次要负责加载器由 sun.misc.Launcher$ExtClassLoader 实现,它负责加载 Java_Home/jre/lib/ext 目录中,或者由 java.ext.dirs 零碎变量指定的门路中的所有类库(如 javax.* 结尾的类),开发者能够间接应用扩大类加载器。
  3. 零碎类加载器(System ClassLoader):次要负责加载器由 sun.misc.Launcher$AppClassLoader 来实现,它负责加载用户类门路(ClassPath)所指定的类,开发者能够间接应用该类加载器,如果应用程序中没有自定义过本人的类加载器,个别状况下这个就是程序中默认的类加载器。
  4. 自定义类加载器(Custom ClassLoader:本人开发的类加载器
  5. 双亲委派准则 :类加载器在加载 class 文件的时候,听从双亲委派准则,意思是加载顺次由父加载器先执行加载动作,只有当父加载器没有加载到 class 文件时才由子类加载器进行加载。这种机制很好的保障了 Java API 的安全性,使得 JDK 的代码不会被篡改。

验证

JVM 会在该阶段对二进制字节流进行校验,只有合乎 JVM 字节码标准的能力被 JVM 正确执行。该阶段是保障 JVM 平安的重要屏障,上面是一些次要的查看。

  • 确保二进制字节流格局合乎预期(比如说是否以 cafe bene 结尾)。
  • 是否所有办法都恪守访问控制关键字的限定。
  • 办法调用的参数个数和类型是否正确。
  • 确保变量在应用之前被正确初始化了。
  • 查看变量是否被赋予失当类型的值。

筹备

JVM 会在该阶段对类变量(也称为动态变量,static 关键字润饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。

此时不会调配实例变量的内存,因为实例变量是在实例化对象时一起创立在 Java 堆中的。而且此时类变量是赋值为零值,即 int 类型的零值为 0,援用类型零值为 null,而不是代码中显示赋值的数值。

解析

该阶段将常量池中的符号援用转化为间接援用。

在 class 文件中常量池外面寄存了字面量和符号援用,符号援用包含类和接口的全限定名以及字段和办法的名称与描述符。

在 JVM 动静链接的时候须要依据这些符号援用来转换为间接援用寄存内存应用。

初始化

该阶段是类加载过程的最初一步。在筹备阶段,类变量曾经被赋过默认初始值,而在初始化阶段,类变量将被赋值为代码冀望赋的值。换句话说,初始化阶段是执行类结构器办法的过程。

三、类加载机会

  • new、getstatic、putstatic、invokestatic 这 4 个字节码指令时对类进行初始化(即:实例化对象、读写动态对象、调用静态方法时,进行类的初始化);
  • 应用反射机制对类进行调用时,进行类的初始化;
  • 初始化一个类,其父类没有初始化时,先初始化其父类;
  • 虚拟机启动时,初始化一个执行主类;
  • 应用 JDK1.7 的动静语言反对时,如果 MethodHandle 实例的解析后果为 REF_getstatic、REF_putstatic、REF_invokestatic 的办法句柄(即:读写动态对象或者调用静态方法),则初始化该句柄对应类。

四、Java 字节码文件中的 JVM 指令

1、创立一个 Java 源文件 Test02.java,并在 main 办法中实现简略的逻辑操作,如下所示。

public class Test02 {public static void main(String[] args) {
        int i = 5;
        int j = 10;
        int k = i + j;
        System.out.println(k);
    }
}

2、在终端通过 javac 命令编译 HelloWorld.java。

javac Test02.java

3、反编译成咱们能看懂的 JVM 指令,这里咱们应用 javap -c 命令实现。

javap -c Test02.class

4、反编译之后的 JVM 指令如下所示。

1    Compiled from "Test02.java"
2    public class org.example.jvm.Test02 {3     public org.example.jvm.Test02();
4        Code:
5           0: aload_0
6           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
7           4: return
8
9      public static void main(java.lang.String[]);
10        Code:
11           0: iconst_5
12           1: istore_1
13           2: bipush        10
14           4: istore_2
15           5: iload_1
16           6: iload_2
17           7: iadd
18           8: istore_3
19           9: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
20          12: iload_3
21          13: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
22          16: return
    }

具体解释一下上述的 JVM 指令

第 1 行示意以后的字节码文件编译自 Test02.java

第 3 行示意调用 Test02 的无参构造函数来实例化以后对象。

第 4 行到第 7 行示意无参构造函数的执行流程。

第 5 行示意把 this 压入操作数栈中。

第 6 行示意调用 Test02 父类 Object 的无参结构,咱们晓得每个对象在实例化的时候都会默认先实例化其父类对象,并且默认调用父类的无参结构。

第 7 行 return 示意构造方法执行结束。

第 10 行到第 22 行示意 main 办法的执行流程。

第 11 行示意将常量 5 压入操作数栈。

第 12 行示意取出操作数栈栈顶元素,即 5,而后保留到局部变量表第 1 个地位,即变量 i。

第 13 行示意将常量 10 压入操作数栈。

第 14 行示意取出操作数栈栈顶元素,即 10,而后保留到局部变量表第 2 个地位,即变量 j。

第 15 行示意将局部变量表第 1 个变量(i)压入操作数栈。

第 16 行示意将局部变量表第 2 个变量(j)压入操作数栈。

第 17 行示意取出操作数栈中的前两个值相加,并将后果压入操作数栈顶。

第 18 行示意取出操作数栈栈顶元素,保留到局部变量表第 3 个地位,即变量 k。

第 19 行示意读取动态实例 PrintStream。

第 20 行示意将局部变量表第 3 个变量(k)压入操作数栈。

第 21 行示意调用 PrintStream 的 println 办法,将操作数栈顶元素(变量 k)输入。

第 22 行 return 示意 main 办法执行结束。

正文完
 0