字节码分析-类加载机制

源码

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>方法中,按照源代码顺序执行:

  1. 第一个静态代码块(字节码偏移28-45)
  2. 打印"第一个静态代码块执行"
  3. staticInt = 20
  4. staticLong = 100L

  5. 第二个静态代码块(字节码偏移47-62)

  6. 打印"第二个静态代码块执行"
  7. STATIC_FINAL_VAR = "Initialized in static block"
  8. staticInt = 30

  9. 第三个静态代码块(字节码偏移65-75)

  10. 打印"第三个静态代码块执行"
  11. 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

类加载阶段

类加载分为三个阶段:

  1. 加载(Loading):将.class文件加载到内存
  2. 链接(Linking):验证、准备、解析
  3. 初始化(Initialization):执行<clinit>方法

初始化时机

JVM会在以下情况触发类的初始化:

  1. 创建类的实例(new)
  2. 访问类的静态变量(非final)
  3. 调用类的静态方法
  4. 使用反射访问类
  5. 初始化子类时,如果父类未初始化,先初始化父类

静态变量访问

静态变量通过getstatic和putstatic指令访问:

// 读取静态变量
15: getstatic     #36  // Field staticInt:I

// 设置静态变量
38: putstatic     #36  // Field staticInt:I

注意事项

  1. <clinit>只执行一次:类加载时只执行一次
  2. 线程安全:JVM保证<clinit>在多线程环境下只执行一次
  3. 执行顺序:按照源代码中的顺序执行
  4. 异常处理:如果<clinit>抛出异常,类无法被使用
  5. 父类优先:子类的<clinit>执行前,会先执行父类的<clinit>

与实例初始化的对比

// 静态初始化(类加载时)
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