美文网首页Java基础
class(四)Class文件加载过程

class(四)Class文件加载过程

作者: Timmy_zzh | 来源:发表于2020-12-13 22:21 被阅读0次
  1. class字节码文件加载到内存过程:装载,链接,初始化
  2. class文件加载时机
  3. 链接又分为三个阶段:验证,准备,解析
  4. 初始化时机
  5. 对象创建时代码执行顺序
  • ClassLoader的主要作用就是用来将class字节码加载到内存中,那JVM加载class文件的具体过程是怎样的呢?
  • 一个class文件被加载到内存中需要经过3大步:装载,链接,初始化
    • 其中链接又细分为:验证,准备,解析3小步
1.class文件加载到内存过程.png

1.装载

  • 装载是指Java虚拟机查找.class文件并生成字节流,然后根据字节流创建java.lang.Class对象的过程
  • 装载过程主要完成3件事
    1. ClassLoader通过一个类的全限定名(包名+类名)来查找.class文件,并生成二进制字节流
      • 其中class字节码文件的来源不一定是.class文件,也可以市jar包,zip包,甚至市来源于网络的字节流
    2. 把.class文件的各个部分分别解析(parse)为JVM内部特定的数据结构,并存储在方法区
    3. 在内存中创建一个java.lang.Class类型的对象:
      • 接下来程序在运行过程中所有对该类的访问都通过这个类对象,也就是这个Class类型的类对象是提供给外界访问该类的接口
加载时机
  • 一个项目经过编译之后,往往会生成大量的.class文件。当程序运行时,jvm并不会一次性的将所有.class文件全部加载到内存中。以下两种情况一般会对class进行装载操作
    • 隐式装载:在程序运行过程中,当碰到通过new等方式生成对象时,系统会隐式调用ClassLoader去装载对应的class到内存中
    • 显示装载:在编写源代码时,主动调用Class.forName()等方法也会进行class装载操作,这种方式通常称为显示装载

2.链接

  • 链接过程分为3步:验证,准备,解析
2.1.验证
  • 验证的目的是为了确保.class文件的字节流中包含的信息符合虚拟机的要求,并且不会危及虚拟机本身的安全。主要包含以下几个方面的检验
    1. 文件格式检验:检验字节流是否符合class文件格式的规范,并且能被当前版本的虚拟机处理
    2. 元数据检验:对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求
    3. 字节码检验:通过数据流和控制流分析,确定程序语义是合法,符合逻辑的
    4. 符号引用检验:符号引用检验可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验

实例:

public class Foo {

    public static void main(String[] args) {
        new Foo().print();
    }

    public void print() {
        int superCode = super.hashCode();
        System.out.println("superCode is:" + superCode);

        int thisCode = hashCode();
        System.out.println("thisCode is:" + thisCode);
    }

    @Override
    public int hashCode() {
        return 123;
    }
}
  • 对应javap 字节码内容:
.../Foo.class
  Last modified 2020-12-10; size 853 bytes
  MD5 checksum cfb1269514e4bd9bb8b8818267ce1152
  Compiled from "Foo.java"
public class com.timmy.javalib._3class.Foo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #16.#28        // java/lang/Object."<init>":()V
   #2 = Class              #29            // com/timmy/ks_tree/test/Foo
   #3 = Methodref          #2.#28         // com/timmy/ks_tree/test/Foo."<init>":()V
   #4 = Methodref          #2.#30         // com/timmy/ks_tree/test/Foo.print:()V
   #5 = Methodref          #16.#31        // java/lang/Object.hashCode:()I
   #6 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #7 = Class              #34            // java/lang/StringBuilder
   #8 = Methodref          #7.#28         // java/lang/StringBuilder."<init>":()V
   #9 = String             #35            // superCode is:
  #10 = Methodref          #7.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #11 = Methodref          #7.#37         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #12 = Methodref          #7.#38         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #13 = Methodref          #39.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #14 = Methodref          #2.#31         // com/timmy/ks_tree/test/Foo.hashCode:()I
  #15 = String             #41            // thisCode is:
  #16 = Class              #42            // java/lang/Object
  #17 = Utf8               <init>
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               print
  #24 = Utf8               hashCode
  #25 = Utf8               ()I
  #26 = Utf8               SourceFile
  #27 = Utf8               Foo.java
  #28 = NameAndType        #17:#18        // "<init>":()V
  #29 = Utf8               com/timmy/ks_tree/test/Foo
  #30 = NameAndType        #23:#18        // print:()V
  #31 = NameAndType        #24:#25        // hashCode:()I
  #32 = Class              #43            // java/lang/System
  #33 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
  #34 = Utf8               java/lang/StringBuilder
  #35 = Utf8               superCode is:
  #36 = NameAndType        #46:#47        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #37 = NameAndType        #46:#48        // append:(I)Ljava/lang/StringBuilder;
  #38 = NameAndType        #49:#50        // toString:()Ljava/lang/String;
  #39 = Class              #51            // java/io/PrintStream
  #40 = NameAndType        #52:#53        // println:(Ljava/lang/String;)V
  #41 = Utf8               thisCode is:
  #42 = Utf8               java/lang/Object
  #43 = Utf8               java/lang/System
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Utf8               append
  #47 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #48 = Utf8               (I)Ljava/lang/StringBuilder;
  #49 = Utf8               toString
  #50 = Utf8               ()Ljava/lang/String;
  #51 = Utf8               java/io/PrintStream
  #52 = Utf8               println
  #53 = Utf8               (Ljava/lang/String;)V
{
  public com.timmy.javalib._3class.Foo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class com/timmy/ks_tree/test/Foo
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: invokevirtual #4                  // Method print:()V
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10

  public void print();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: invokespecial #5                  // Method java/lang/Object.hashCode:()I
         4: istore_1
         5: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         8: new           #7                  // class java/lang/StringBuilder
        11: dup
        12: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
        15: ldc           #9                  // String superCode is:
        17: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: iload_1
        21: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        24: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: aload_0
        31: invokevirtual #14                 // Method hashCode:()I
        34: istore_2
        35: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        38: new           #7                  // class java/lang/StringBuilder
        41: dup
        42: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
        45: ldc           #15                 // String thisCode is:
        47: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        50: iload_2
        51: invokevirtual #11                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        54: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        57: invokevirtual #13                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        60: return
      LineNumberTable:
        line 10: 0
        line 11: 5
        line 13: 30
        line 14: 35
        line 15: 60

  public int hashCode();
    descriptor: ()I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: bipush        123
         2: ireturn
      LineNumberTable:
        line 19: 0
}
SourceFile: "Foo.java"
2.2.准备

准备是链接的第2步,这一阶段的主要目的是为类中的静态变量(类变量)分配内存,并为其设置“0值”,比如:

public static int value = 100;
  • 在准备阶段,jvm会为value分配内存,并将其设置为0.
    • 而真正的值100是在初始化阶段设置,并且此阶段进行内存分配的仅包括类变量,而不包括实例变量
    • 实例变量将会在对象实例化时随着对象一起分配在java堆中
  • 静态常量
public static final int value = 100;

静态常量在准备阶段就为value分配内存,并设置为100.

2.3.解析
  • 解析是链接的最后一步,这一阶段的任务是把常量池中的符号引用转换为直接引用,也就是具体的内存地址。
    • 在这一阶段,jvm会将常量池中的类,接口名,字段名,方法名等转换为具体的内存地址

如在Foo类中的main方法中调用Foo.print()方法

   #4 = Methodref          #2.#30         // com/timmy/ks_tree/test/Foo.print:()V

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class com/timmy/ks_tree/test/Foo
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: invokevirtual #4                  // Method print:()V
        10: return
      LineNumberTable:
        line 6: 0
        line 7: 10
  • 在main方法中通过invokevirtual指令调用了print方法
    • “Foo.print:()V”就是一个符号引用,当main方法执行到此处时,会将符号引用“Foo.print:()V”解析(resolve)成直接引用,可以将直接引用理解为方法真正的内存地址。

3.初始化

  • 这时class加载的最后一步,这一阶段是执行类构造器<clinit>方法的过程,并真正初始化类变量,比如:
public static int value = 100;

在准备阶段value被分配内存并设置为0,在初始化阶段value就会被设置为100

3.1.初始化时机
  • 对于装载阶段,jvm没有规范何时具体执行,但是对于初始化,jvm规范中严格规定了class初始化的时机,主要又以下几种情况会触发class的初始化:
    1. 虚拟机启动时,初始化包含main方法的主类;
    2. 遇到new指令创建对象实例时,如果目标对象类没有被初始化则进行初始化操作
    3. 当遇到访问静态方法或者静态字段的指令时,如果目标对象类没有被初始化则进行初始化操作
    4. 子类的初始化过程如果方法其父类还没有进行过初始化,则需要先触发其父类的初始化
    5. 使用反射API进行反射调用时,如果类没有进行过初始化则需要先触发器初始化
    6. 第一次调用java.lang.invoke.MethodHandle实例时,需要初始化MethodHandler指向方法所在的
3.2.初始化类变量(static)
  • 在初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是又static关键字修饰的信息,而没有static修饰的语句块在实例化对象的时候才会执行
public class ClassInit {

    public static int value = 100;

    //静态代码块在初始化阶段执行
    static {
        System.out.println("ClassInit static block ");
    }

    //非静态代码块只在创建对象实例时被执行
    {
        System.out.println("ClassInit not static block ");
    }
}

1.调用静态变量只执行静态代码块:

public class Foo {
    public static void main(String[] args) {
        ClassInit.value = 2;
    }
}
    
> Task :testlib:Foo.main()
ClassInit static block

2.new创建对象实例时执行非静态代码块:

    public static void main(String[] args) {
        ClassInit.value = 2;
        ClassInit classInit = new ClassInit();
    }

> Task :testlib:Foo.main()
ClassInit static block 
ClassInit not static block 
3.3.被动引用
  • 上诉的6中情况在jvm中被称为主动引用,除此6种情况外所有引用类的方法都被称为被动引用。被动引用并不会触发class的初始化
    • 最典型的例子就是子类中调用父类的静态变量
public class Parent {

    public  static int value = 1;

    static {
        System.out.println("this is parent static block ");
    }
}

public class Child extends Parent {

    static {
        System.out.println("this is child static block ");
    }
}
  • Child继承自Parent类,直接使用Child来访问Parent中的value值,不会初始化Child类
public class Foo {

    public static void main(String[] args) {
        Child.value = 3;
    }
}

打印结果:
> Task :testlib:Foo.main()
this is parent static block 
  • 可以看出,Child中的静态代码块并没有被执行,jvm没有对Child执行初始化操作。
    • 对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过子类Child来引用父类Parent中定义的静态字段,只会触发父类Parent的初始化而不会触发子类Child的初始化。
3.4.class初始化和对象的创建顺序
  • 当在代码中使用new关键字创建一个类的实例对象时,类中的静态代码块,非静态代码块,构造函数之间的执行顺序如何呢?
public class ProcessOrder {

    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println("=======================");
        p = new Child();
    }

    static class Child extends Parent {
        static {
            System.out.println("Child static block");
        }

        {
            System.out.println("Child not-static block");
        }

        public Child() {
            System.out.println("Child constructor");
        }
    }

    static class Parent {

        static {
            System.out.println("parent static block");
        }

        {
            System.out.println("parent not-static block");
        }

        public Parent() {
            System.out.println("parent constructor");
        }
    }
}

日志打印:
> Task :testlib:ProcessOrder.main()
parent static block
Child static block
parent not-static block
parent constructor
Child not-static block
Child constructor
=======================
parent not-static block
parent constructor
Child not-static block
Child constructor
  • 在main方法中执行了2此new Child()的操作,对象的初始化顺序如下:

静态变量/静态代码块 -》普通代码块 -》 构造函数

  1. 父类静态变量和静态代码块
  2. 子类静态变量和静态代码块
  3. 父类普通成员变量和普通代码块
  4. 父类构造函数
  5. 子类普通成员变量和普通代码块
  6. 子类构造函数

相关文章

  • class(四)Class文件加载过程

    class字节码文件加载到内存过程:装载,链接,初始化 class文件加载时机 链接又分为三个阶段:验证,准备,解...

  • 2018-11-19

    jvm中Class装载系统 Class文件加载过程 ClassLoader的工作模式 文件加载过程加载----->...

  • JVM加载类过程

    1 类加载总过程 类加载子系统负责从文件系统或者网络加载Class文件,Class文件在文件开头有特定的文件标识i...

  • JVM类加载器子系统ClassLoader

    1 类加载器与类的加载过程 类加载器子系统的作用:类加载子系统负责从文件或者网络中加载class文件,class文...

  • Java类加载过程(干货)

    类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? ...

  • 【搞定Jvm面试】 面试官:谈谈 JVM 类加载过程是怎样的?

    类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? ...

  • JVM必问知识点:类加载过程

    类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢? ...

  • JAVA学习笔记0x03

    类加载 把.class文件从硬盘读取到内存(jvm)中,将这个过程称为类加载 jvm搜索.class文件位置的顺序...

  • Java中高级核心知识全面解析——类加载过程

    一、类加载过程 Class 文件需要加载到虚拟机中之后才能运行和使用,那么虚拟机是如何加载这些 Class 文件呢...

  • java基础概念

    编译运行过程 1.java源文件经过编译生成.class文件(字节码文件)2.JVM加载.class文件并且运行....

网友评论

    本文标题:class(四)Class文件加载过程

    本文链接:https://www.haomeiwen.com/subject/fnqmgktx.html