一、准备阶段
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。对于该阶段有以下几点需要注意:
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,分别对应几个方法,联系动态绑定的那篇文章,应该就比较清晰了。