字节码分析-类加载机制
源码
package java.bytecode;
/**
* 类加载机制示例 - 演示静态变量和静态代码块的字节码
* 重点研究类加载阶段的初始化机制
*/
public class ClassLoadingExample {
// 静态变量 - 基本类型(带初始值)
private static int staticInt = 10;
// 静态变量 - 基本类型(不带初始值,使用默认值)
private static long staticLong;
// 静态变量 - 对象类型
private static String staticString = "Hello";
// 静态final常量(编译时常量)
public static final int STATIC_FINAL_CONSTANT = 100;
// 静态final变量(需要运行时初始化)
private static final String STATIC_FINAL_VAR;
// 静态变量 - 数组
private static int[] staticArray = new int[]{1, 2, 3};
// 第一个静态代码块
static {
System.out.println("第一个静态代码块执行");
staticInt = 20;
staticLong = 100L;
}
// 第二个静态代码块(演示多个静态代码块的执行顺序)
static {
System.out.println("第二个静态代码块执行");
STATIC_FINAL_VAR = "Initialized in static block";
staticInt = 30;
}
// 第三个静态代码块
static {
System.out.println("第三个静态代码块执行");
staticString = "World";
}
// 实例变量(用于对比)
private int instanceInt = 50;
// 构造方法
public ClassLoadingExample() {
System.out.println("构造方法执行");
this.instanceInt = 100;
}
// 实例方法
public void instanceMethod() {
System.out.println("实例方法执行,staticInt = " + staticInt);
}
// 静态方法
public static void staticMethod() {
System.out.println("静态方法执行,staticInt = " + staticInt);
}
// 获取静态变量
public static int getStaticInt() {
return staticInt;
}
public static String getStaticFinalVar() {
return STATIC_FINAL_VAR;
}
}
结构化反解析
Classfile /D:/zcp/github/yggdrasil/java/bytecode/ClassLoadingExample.class
Last modified 2025-12-4; size 1612 bytes
MD5 checksum 437aad277ebf3437a3acbd5802f2467c
Compiled from "ClassLoadingExample.java"
public class java.bytecode.ClassLoadingExample
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5.#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
...
#77 = Utf8 STATIC_FINAL_CONSTANT
#78 = Utf8 ConstantValue
#79 = Integer 100
...
#87 = Utf8 <clinit>
#88 = Utf8 SourceFile
#89 = Utf8 ClassLoadingExample.java
{
private static int staticInt;
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC
private static long staticLong;
descriptor: J
flags: ACC_PRIVATE, ACC_STATIC
private static java.lang.String staticString;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
public static final int STATIC_FINAL_CONSTANT;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 100
private static final java.lang.String STATIC_FINAL_VAR;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
private static int[] staticArray;
descriptor: [I
flags: ACC_PRIVATE, ACC_STATIC
private int instanceInt;
descriptor: I
flags: ACC_PRIVATE
public java.bytecode.ClassLoadingExample();
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: bipush 50
7: putfield #7 // Field instanceInt:I
10: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #19 // String 构造方法执行
15: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: aload_0
19: bipush 100
21: putfield #7 // Field instanceInt:I
24: return
LineNumberTable:
line 51: 0
line 48: 4
line 52: 10
line 53: 18
line 54: 24
public void instanceMethod();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #27 // class java/lang/StringBuilder
6: dup
7: invokespecial #29 // Method java/lang/StringBuilder."<init>":()V
10: ldc #30 // String 实例方法执行,staticInt =
12: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: getstatic #36 // Field staticInt:I
18: invokevirtual #39 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: invokevirtual #42 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
LineNumberTable:
line 58: 0
line 59: 27
public static void staticMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=0, args_size=0
0: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #27 // class java/lang/StringBuilder
6: dup
7: invokespecial #29 // Method java/lang/StringBuilder."<init>":()V
10: ldc #46 // String 静态方法执行,staticInt =
12: invokevirtual #32 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: getstatic #36 // Field staticInt:I
18: invokevirtual #39 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: invokevirtual #42 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
LineNumberTable:
line 63: 0
line 64: 27
public static int getStaticInt();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #36 // Field staticInt:I
3: ireturn
LineNumberTable:
line 68: 0
public static java.lang.String getStaticFinalVar();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #48 // Field STATIC_FINAL_VAR:Ljava/lang/String;
3: areturn
LineNumberTable:
line 72: 0
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: bipush 10
2: putstatic #36 // Field staticInt:I
5: ldc #52 // String Hello
7: putstatic #54 // Field staticString:Ljava/lang/String;
10: iconst_3
11: newarray int
13: dup
14: iconst_0
15: iconst_1
16: iastore
17: dup
18: iconst_1
19: iconst_2
20: iastore
21: dup
22: iconst_2
23: iconst_3
24: iastore
25: putstatic #57 // Field staticArray:[I
28: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
31: ldc #61 // String 第一个静态代码块执行
33: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
36: bipush 20
38: putstatic #36 // Field staticInt:I
41: ldc2_w #63 // long 100l
44: putstatic #65 // Field staticLong:J
47: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
50: ldc #69 // String 第二个静态代码块执行
52: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: ldc #71 // String Initialized in static block
57: putstatic #48 // Field STATIC_FINAL_VAR:Ljava/lang/String;
60: bipush 30
62: putstatic #36 // Field staticInt:I
65: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
68: ldc #73 // String 第三个静态代码块执行
70: invokevirtual #21 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
73: ldc #75 // String World
75: putstatic #54 // Field staticString:Ljava/lang/String;
78: return
LineNumberTable:
line 10: 0
line 16: 5
line 25: 10
line 29: 28
line 30: 36
line 31: 41
line 36: 47
line 37: 55
line 38: 60
line 43: 65
line 44: 73
line 45: 78
}
SourceFile: "ClassLoadingExample.java"
详细说明
<clinit>方法
<clinit>是类初始化方法(Class Initialization),用于初始化静态变量和执行静态代码块。该方法由JVM在类加载的初始化阶段自动调用。
特点:
- 方法名:<clinit>
- 访问标志:ACC_STATIC
- 无参数,无返回值
- 只能由JVM调用,不能手动调用
静态变量初始化顺序
编译器会将所有静态变量的初始化和静态代码块合并到一个<clinit>方法中,按照源代码中的顺序执行:
static {};
Code:
0: bipush 10 // staticInt = 10 (静态变量初始化)
2: putstatic #36
5: ldc #52 // staticString = "Hello" (静态变量初始化)
7: putstatic #54
10: iconst_3 // staticArray初始化
11: newarray int
...
25: putstatic #57
28: getstatic #13 // 第一个静态代码块开始
31: ldc #61
33: invokevirtual #21
36: bipush 20 // staticInt = 20 (第一个静态代码块)
38: putstatic #36
41: ldc2_w #63 // staticLong = 100L (第一个静态代码块)
44: putstatic #65
47: getstatic #13 // 第二个静态代码块开始
...
65: getstatic #13 // 第三个静态代码块开始
...
78: return
静态变量初始化方式
1. 编译时常量(ConstantValue)
对于编译时常量,使用ConstantValue属性,不需要在<clinit>中初始化:
public static final int STATIC_FINAL_CONSTANT;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 100
2. 运行时初始化
对于需要在运行时初始化的静态变量,在<clinit>方法中初始化:
private static int staticInt = 10;
// 在<clinit>中:
0: bipush 10
2: putstatic #36
多个静态代码块的合并
所有静态代码块会被合并到一个<clinit>方法中,按照源代码顺序执行:
- 第一个静态代码块(字节码偏移28-45)
- 打印"第一个静态代码块执行"
staticInt = 20-
staticLong = 100L -
第二个静态代码块(字节码偏移47-62)
- 打印"第二个静态代码块执行"
STATIC_FINAL_VAR = "Initialized in static block"-
staticInt = 30 -
第三个静态代码块(字节码偏移65-75)
- 打印"第三个静态代码块执行"
staticString = "World"
数组初始化
静态数组的初始化在<clinit>中完成:
10: iconst_3 // 数组长度3
11: newarray int // 创建int数组
13: dup
14: iconst_0 // 索引0
15: iconst_1 // 值1
16: iastore // array[0] = 1
17: dup
18: iconst_1 // 索引1
19: iconst_2 // 值2
20: iastore // array[1] = 2
21: dup
22: iconst_2 // 索引2
23: iconst_3 // 值3
24: iastore // array[2] = 3
25: putstatic #57 // 存储到staticArray
类加载阶段
类加载分为三个阶段:
- 加载(Loading):将.class文件加载到内存
- 链接(Linking):验证、准备、解析
- 初始化(Initialization):执行
<clinit>方法
初始化时机
JVM会在以下情况触发类的初始化:
- 创建类的实例(
new) - 访问类的静态变量(非final)
- 调用类的静态方法
- 使用反射访问类
- 初始化子类时,如果父类未初始化,先初始化父类
静态变量访问
静态变量通过getstatic和putstatic指令访问:
// 读取静态变量
15: getstatic #36 // Field staticInt:I
// 设置静态变量
38: putstatic #36 // Field staticInt:I
注意事项
<clinit>只执行一次:类加载时只执行一次- 线程安全:JVM保证
<clinit>在多线程环境下只执行一次 - 执行顺序:按照源代码中的顺序执行
- 异常处理:如果
<clinit>抛出异常,类无法被使用 - 父类优先:子类的
<clinit>执行前,会先执行父类的<clinit>
与实例初始化的对比
- 静态初始化:
<clinit>方法,类加载时执行一次 - 实例初始化:
<init>方法(构造方法),每次创建对象时执行
// 静态初始化(类加载时)
static {};
Code:
0: bipush 10
2: putstatic #36
// 实例初始化(对象创建时)
public ClassLoadingExample();
Code:
0: aload_0
1: invokespecial #1
4: aload_0
5: bipush 50
7: putfield #7 // Field instanceInt:I