类加载机制学习补充

一、准备阶段

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:

1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。

2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式地赋予的值。

假设一个类变量的定义为:public static int value = 3;那么变量value在准备阶段过后的初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的putstatic指令是在程序编译后,存放于类构造器clinit()方法之中的,所以把value赋值为3的动作将在初始化阶段才会执行。

3、如果类字段的字段属性表中存在ConstantValue属性,即同时被final和static修饰,那么在准备阶段变量value就会被初始化为ConstValue属性所指定的值。

但是注意,并不是变量拥有ConstValue属性,就会在准备阶段被赋初值,前提是必须是static修饰的类字段。比如变量定义 public final int value = 3;value 字段也会存在ConstantValue属性,但是其赋值是在实例构造器init方法中执行的。

关于类构造器clinit()与实例构造器init()方法的解释,其中也测试了关于静态代码块,非静态代码块,变量初始化语句,构造函数的执行顺序问题,值得一看。
参考文章https://blog.youkuaiyun.com/lerous/article/details/113760668

public class Demo {

    public static final Super s = new Super();
    public static final int a = 1;
    public static int b = 2;

    public final int c = 2;
    public final int d;
    public int e = 2; // 初始化语句的执行优先级高于构造器,与代码块的执行顺序取决于位置顺序

    public Demo() {
        d = 4;
//        e = 6;
    }

    {

        b = 3;  // 多个"{}"按先后顺序最终合并到<init>中
//        d = 2;  // 代码块的优先级高于构造函数中的赋值动作,与代码位置无关,注释放开会导致构造器中的d=4语句报错
        e = 5;  // 提示此语句是无用的赋值,因为始终会被唯一构造方法中的e=6覆盖掉
    }


    public static void method() {
        int a = 1;
    }

    public void method2() {
        String s = "method2";
    }

    public final int method3() {
        return 5;
    }

    static {
//        int t = f; // 报错,非法向前引用,静态语句块对于定义在其后的边量,只能进行赋值,但不能访问
        b = 5;   // 静态代码块与静态变量的赋值语句的执行顺序取决于代码的位置顺序,多个静态块按顺序合并到cinit方法中
    }

    public static int f = 7;

    public static void main(String[] args) {

    }
}

再看一下反编译class文件的内容

Classfile /C:/Users/Administrator/Desktop/Demo.class
  Last modified 2022-2-10; size 772 bytes
  MD5 checksum 4ef38358d697751706e61bc7e6256ea4
  Compiled from "Demo.java"
public class Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#38        // java/lang/Object."<init>":()V
   #2 = Fieldref           #11.#39        // Demo.c:I
   #3 = Fieldref           #11.#40        // Demo.b:I
   #4 = Fieldref           #11.#41        // Demo.e:I
   #5 = Fieldref           #11.#42        // Demo.d:I
   #6 = String             #30            // method2
   #7 = Class              #43            // Super
   #8 = Methodref          #7.#38         // Super."<init>":()V
   #9 = Fieldref           #11.#44        // Demo.s:LSuper;
  #10 = Fieldref           #11.#45        // Demo.f:I
  #11 = Class              #46            // Demo
  #12 = Class              #47            // java/lang/Object
  #13 = Utf8               s
  #14 = Utf8               LSuper;
  #15 = Utf8               a
  #16 = Utf8               I
  #17 = Utf8               ConstantValue
  #18 = Integer            1
  #19 = Utf8               b
  #20 = Utf8               c
  #21 = Integer            2
  #22 = Utf8               d
  #23 = Utf8               e
  #24 = Utf8               f
  #25 = Utf8               <init>
  #26 = Utf8               ()V
  #27 = Utf8               Code
  #28 = Utf8               LineNumberTable
  #29 = Utf8               method
  #30 = Utf8               method2
  #31 = Utf8               method3
  #32 = Utf8               ()I
  #33 = Utf8               main
  #34 = Utf8               ([Ljava/lang/String;)V
  #35 = Utf8               <clinit>
  #36 = Utf8               SourceFile
  #37 = Utf8               Demo.java
  #38 = NameAndType        #25:#26        // "<init>":()V
  #39 = NameAndType        #20:#16        // c:I
  #40 = NameAndType        #19:#16        // b:I
  #41 = NameAndType        #23:#16        // e:I
  #42 = NameAndType        #22:#16        // d:I
  #43 = Utf8               Super
  #44 = NameAndType        #13:#14        // s:LSuper;
  #45 = NameAndType        #24:#16        // f:I
  #46 = Utf8               Demo
  #47 = Utf8               java/lang/Object
{
  public static final Super s;
    descriptor: LSuper;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public static final int a;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 1

  public static int b;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public final int c;
    descriptor: I
    flags: ACC_PUBLIC, ACC_FINAL
    ConstantValue: int 2

  public final int d;
    descriptor: I
    flags: ACC_PUBLIC, ACC_FINAL

  public int e;
    descriptor: I
    flags: ACC_PUBLIC

  public static int f;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  public Demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: iconst_2
         6: putfield      #2                  // Field c:I
         9: iconst_3
        10: putstatic     #3                  // Field b:I
        13: aload_0
        14: iconst_5
        15: putfield      #4                  // Field e:I
        18: aload_0
        19: iconst_4
        20: putfield      #5                  // Field d:I
        23: aload_0
        24: bipush        6
        26: putfield      #4                  // Field e:I
        29: return
      LineNumberTable:
        line 11: 0
        line 7: 4
        line 17: 9
        line 19: 13
        line 12: 18
        line 13: 23
        line 14: 29

  public static void method();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: iconst_1
         1: istore_0
         2: return
      LineNumberTable:
        line 24: 0
        line 25: 2

  public void method2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #6                  // String method2
         2: astore_1
         3: return
      LineNumberTable:
        line 28: 0
        line 29: 3

  public final int method3();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=1, locals=1, args_size=1
         0: iconst_5
         1: ireturn
      LineNumberTable:
        line 32: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 43: 0

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #7                  // class Super
         3: dup
         4: invokespecial #8                  // Method Super."<init>":()V
         7: putstatic     #9                  // Field s:LSuper;
        10: iconst_2
        11: putstatic     #3                  // Field b:I
        14: iconst_5
        15: putstatic     #3                  // Field b:I
        18: bipush        7
        20: putstatic     #10                 // Field f:I
        23: return
      LineNumberTable:
        line 3: 0
        line 5: 10
        line 36: 14
        line 39: 18
}
SourceFile: "Demo.java"

对比着简单了解一下字节码指令

二、解析阶段

类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

对于红字部分如何理解,什么情况下,解析阶段在初始化阶段之后开始?又如何关系到动态绑定?
在这里插入图片描述再参考一篇关于动态绑定的文章:https://www.cnblogs.com/ShaneZhang/p/4972550.html

Java是一门面向对象的编程语言,优势就在于支持多态(Polymorphism)。多态使得父类型的引用变量可以引用子类型的对象。如果调用子类型对象的一个虚方法(非private,final or static),编译器将无法找到真正需要调用的方法,因为它可能是定义在父类型中的方法,也可能是在子类型中被重写(override)的方法,这种情形,只能在运行时进行解析,因为只有在运行时期,才能明确具体的对象到底是什么。这也是我们俗称的运行时或动态绑定(runtime or dynamic binding)。

结合代码demo,理解一下。

public class Demo2 {
    public static void main(String[] args) {
        Super s = new Son();
        s.toString();
        s.method();
        s.method2();
        Super.method3();
    }
}

public class Super {

    void method() {
        // 被子类重写
    }

    void method2() {
    }

    static void method3() {
    }
}

public class Son extends Super {

    @Override
    void method() {
        super.method();
    }
}

对Demo2反编译后的字节码内容,主要看main方法

Classfile /C:/Users/Administrator/Desktop/Demo2.class
  Last modified 2022-2-10; size 427 bytes
  MD5 checksum ddabcb21a153230b2b2cef3df7ece4f2
  Compiled from "Demo2.java"
public class Demo2
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#18         // java/lang/Object."<init>":()V
   #2 = Class              #19            // Son
   #3 = Methodref          #2.#18         // Son."<init>":()V
   #4 = Methodref          #9.#20         // java/lang/Object.toString:()Ljava/lang/String;
   #5 = Methodref          #21.#22        // Super.method:()V
   #6 = Methodref          #21.#23        // Super.method2:()V
   #7 = Methodref          #21.#24        // Super.method3:()V
   #8 = Class              #25            // Demo2
   #9 = Class              #26            // java/lang/Object
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = Utf8               Code
  #13 = Utf8               LineNumberTable
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               SourceFile
  #17 = Utf8               Demo2.java
  #18 = NameAndType        #10:#11        // "<init>":()V
  #19 = Utf8               Son
  #20 = NameAndType        #27:#28        // toString:()Ljava/lang/String;
  #21 = Class              #29            // Super
  #22 = NameAndType        #30:#11        // method:()V
  #23 = NameAndType        #31:#11        // method2:()V
  #24 = NameAndType        #32:#11        // method3:()V
  #25 = Utf8               Demo2
  #26 = Utf8               java/lang/Object
  #27 = Utf8               toString
  #28 = Utf8               ()Ljava/lang/String;
  #29 = Utf8               Super
  #30 = Utf8               method
  #31 = Utf8               method2
  #32 = Utf8               method3
{
  public Demo2();
    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 7: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Son
         3: dup
         4: invokespecial #3                  // Method Son."<init>":()V
         7: astore_1
         8: aload_1
         9: invokevirtual #4                  // Method java/lang/Object.toString:()Ljava/lang/String;
        12: pop
        13: aload_1
        14: invokevirtual #5                  // Method Super.method:()V
        17: aload_1
        18: invokevirtual #6                  // Method Super.method2:()V
        21: invokestatic  #7                  // Method Super.method3:()V
        24: return
      LineNumberTable:
        line 10: 0
        line 11: 8
        line 12: 13
        line 13: 17
        line 14: 21
        line 15: 24
}
SourceFile: "Demo2.java"

注意到,9、14、18、21这几个指令都属于上面提到的必须在使用前对字符引用进行解析,这里的字符引用即#4、#5、#6、#7,分别对应几个方法,联系动态绑定的那篇文章,应该就比较清晰了。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值