学习内容目录:

        1. 类文件结构
        2. 字节码指令
        3. 编译期处理
        4. 类加载阶段

        5. 类加载器
        6. 运行期优化

JVM学习笔记(四)类加载与字节码技术-LMLPHP

一、类文件结构

根据 JVM 规范,类文件结构如下

JVM学习笔记(四)类加载与字节码技术-LMLPHP

二、字节码指令

反编译命令:javap -v Xxx.class

2.3 图解方法执行流程

1)原始 java 代码

package org.wuya.test;

/**
 * 演示 字节码指令 和 操作数栈、常量池的关系
 */
public class Demo3_1 {
    public static void main(String[] args) {
        int a = 10;
        int b = Short.MAX_VALUE + 1;
        int c = a + b;
        System.out.println(c);
    }
}

2)编译后的字节码文件

3)常量池载入运行时常量池

4)方法字节码载入方法区

JVM学习笔记(四)类加载与字节码技术-LMLPHP

5)main 线程开始运行,分配栈帧内存

(stack=2,locals=4)

JVM学习笔记(四)类加载与字节码技术-LMLPHP

6)执行引擎开始执行字节码★★★

bipush 10
将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有

  • sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
  • ldc 将一个 int 压入操作数栈
  • ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)

这里小的数字都是和字节码指令存在一起,超过 short 范围的数字存入了常量池。

invokevirtual #5

  • 找到常量池 #5 项
  • 定位到方法区 java/io/PrintStream.println:(I)V 方法
  • 生成新的栈帧(分配 locals、stack等)
  • 传递参数,执行新栈帧中的字节码

JVM学习笔记(四)类加载与字节码技术-LMLPHP

  • 执行完毕,弹出栈帧
  • 清除 main 操作数栈内容 

JVM学习笔记(四)类加载与字节码技术-LMLPHP

2.4 练习 - 分析 i++

1)源码如下:

package org.wuya.test;

/**
 * 从字节码角度分析 a++  相关题目
 */
public class Demo3_2 {
    public static void main(String[] args) {
        int a = 10;
        int b = a++ + ++a + a--;
        System.out.println(a);
        System.out.println(b);
    }
}

2)编译后的字节码文件:

重点看下面这段

分析:

  • 注意 iinc 指令是直接在局部变量 slot 上进行运算
  • a++ 和 ++a 的区别是先执行 iload 还是 先执行 iinc

a++ 是先iload再自增(iinc),++a 是先自增(iinc)再iload。

具体分析见上面。图解见讲义。

2.5 条件判断指令

讲义有一张指令代表的含义,略。

几点说明:

  • byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节
  • goto 用来进行跳转到指定行号的字节码
  • 比较小的整数(从-1到5)是用iconst来表示的。

源码:

package org.wuya.test;

public class Demo3_3 {
    public static void main(String[] args) {
        int a = 0;
        if(a == 0) {
            a = 10;
        } else {
            a = 20;
        }
    }
}

关键的字节码:

2.6 循环控制指令

其实循环控制还是前面介绍的那些指令,例如 while 循环,do while 循环,字节码略。

public class Demo3_4 {
   public static void main(String[] args) {
       int a = 0;
       while (a < 10) {
           a++;
      }
  }
}



public class Demo3_5 {
   public static void main(String[] args) {
       int a = 0;
       do {
           a++;
      } while (a < 10);
  }
}

再看看 for 循环,比较 while 和 for 的字节码,你发现它们是一模一样的。

2.7 练习 - 判断结果

请从字节码角度分析,下列代码运行的结果:

package org.wuya.test;

public class Demo3_6_1 {
    public static void main(String[] args) {
        int i = 0;
        int x = 0;
        while (i < 10) {
            x = x++;
            i++;
        }
        System.out.println(x);// 结果是 0
    }
}

关键字节码:

2.8 构造方法★★★

1) <cinit>()V   类构造方法

源码:

package org.wuya.test;

public class Demo3_8_1 {

    static {
        i = 20;
    }


    static {
        i = 30;
    }

    static int i = 10;

}

JVM学习笔记(四)类加载与字节码技术-LMLPHP 

反编译后的字节码:

编译器会按(源码中)从上至下的顺序,收集所有 static 静态代码块静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V :

<cinit>()V 方法会在类加载的初始化阶段被调用。

2) <init>()V   实例构造方法

源码:

package org.wuya.test;

public class Demo3_8_2 {


    private String a = "s1";

    {
        b = 20;
    }

    private int b = 10;

    {
        a = "s2";
    }

    public Demo3_8_2(String a, int b) {
        this.a = a;
        this.b = b;
    }

    public static void main(String[] args) {
        Demo3_8_2 d = new Demo3_8_2("s3", 30);
        System.out.println(d.a);
        System.out.println(d.b);
    }
}

JVM学习笔记(四)类加载与字节码技术-LMLPHP

下面是反编译后的字节码:

PS D:\JavaTools\...\target\classes\org\wuya\test> javap -v .\Demo3_8_2.class
Classfile /D:/JavaTools/.../target/classes/org/wuya/test/Demo3_8_2.class
  Last modified 2024-4-21; size 859 bytes                                                
  MD5 checksum 987873efec13bf5b024a1af1a2d71201                                          
  Compiled from "Demo3_8_2.java"                                                         
public class org.wuya.test.Demo3_8_2                                                     
  minor version: 0                                                                       
  major version: 52                                                                      
  flags: ACC_PUBLIC, ACC_SUPER                                                           
Constant pool:                                                                           
   #1 = Methodref          #12.#32        // java/lang/Object."<init>":()V               
   #2 = String             #33            // s1                                          
   #3 = Fieldref           #6.#34         // org/wuya/test/Demo3_8_2.a:Ljava/lang/String;
   #4 = Fieldref           #6.#35         // org/wuya/test/Demo3_8_2.b:I                 
   #5 = String             #36            // s2                                          
   #6 = Class              #37            // org/wuya/test/Demo3_8_2                     
   #7 = String             #38            // s3                                          
   #8 = Methodref          #6.#39         // org/wuya/test/Demo3_8_2."<init>":(Ljava/lang/String;I)V
   #9 = Fieldref           #40.#41        // java/lang/System.out:Ljava/io/PrintStream;
  #10 = Methodref          #42.#43        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Methodref          #42.#44        // java/io/PrintStream.println:(I)V
  #12 = Class              #45            // java/lang/Object
  #13 = Utf8               a
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               b
  #16 = Utf8               I
  #17 = Utf8               <init>
  #18 = Utf8               (Ljava/lang/String;I)V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Lorg/wuya/test/Demo3_8_2;
  #24 = Utf8               MethodParameters
  #25 = Utf8               main
  #26 = Utf8               ([Ljava/lang/String;)V
  #27 = Utf8               args
  #28 = Utf8               [Ljava/lang/String;
  #29 = Utf8               d
  #30 = Utf8               SourceFile
  #31 = Utf8               Demo3_8_2.java
  #32 = NameAndType        #17:#46        // "<init>":()V
  #33 = Utf8               s1
  #34 = NameAndType        #13:#14        // a:Ljava/lang/String;
  #35 = NameAndType        #15:#16        // b:I
  #36 = Utf8               s2
  #37 = Utf8               org/wuya/test/Demo3_8_2
  #38 = Utf8               s3
  #39 = NameAndType        #17:#18        // "<init>":(Ljava/lang/String;I)V
  #40 = Class              #47            // java/lang/System
  #41 = NameAndType        #48:#49        // out:Ljava/io/PrintStream;
  #42 = Class              #50            // java/io/PrintStream
  #43 = NameAndType        #51:#52        // println:(Ljava/lang/String;)V
  #44 = NameAndType        #51:#53        // println:(I)V
  #45 = Utf8               java/lang/Object
  #46 = Utf8               ()V
  #47 = Utf8               java/lang/System
  #48 = Utf8               out
  #49 = Utf8               Ljava/io/PrintStream;
  #50 = Utf8               java/io/PrintStream
  #51 = Utf8               println
  #52 = Utf8               (Ljava/lang/String;)V
  #53 = Utf8               (I)V
{
  public org.wuya.test.Demo3_8_2(java.lang.String, int);
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String s1
         7: putfield      #3                  // Field a:Ljava/lang/String;
        10: aload_0
        11: bipush        20
        13: putfield      #4                  // Field b:I
        16: aload_0
        17: bipush        10
        19: putfield      #4                  // Field b:I
        22: aload_0
        23: ldc           #5                  // String s2
        25: putfield      #3                  // Field a:Ljava/lang/String;
        28: aload_0
        29: aload_1
        30: putfield      #3                  // Field a:Ljava/lang/String;
        33: aload_0
        34: iload_2
        35: putfield      #4                  // Field b:I
        38: return
      LineNumberTable:
        line 18: 0
        line 6: 4
        line 9: 10
        line 12: 16
        line 15: 22
        line 19: 28
        line 20: 33
        line 21: 38
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      39     0  this   Lorg/wuya/test/Demo3_8_2;
            0      39     1     a   Ljava/lang/String;
            0      39     2     b   I
    MethodParameters:
      Name                           Flags
      a
      b

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=2, args_size=1
         0: new           #6                  // class org/wuya/test/Demo3_8_2
         3: dup
         4: ldc           #7                  // String s3
         6: bipush        30
         8: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
        11: astore_1
        12: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: aload_1
        16: getfield      #3                  // Field a:Ljava/lang/String;
        19: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
        25: aload_1
        26: getfield      #4                  // Field b:I
        29: invokevirtual #11                 // Method java/io/PrintStream.println:(I)V
        32: return
      LineNumberTable:
        line 24: 0
        line 25: 12
        line 26: 22
        line 27: 32
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0  args   [Ljava/lang/String;
           12      21     1     d   Lorg/wuya/test/Demo3_8_2;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Demo3_8_2.java"

编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后。

2.9 方法调用

04-22 08:52