学习内容目录:
1. 类文件结构
2. 字节码指令
3. 编译期处理
4. 类加载阶段
5. 类加载器
6. 运行期优化
一、类文件结构
根据 JVM 规范,类文件结构如下
二、字节码指令
反编译命令: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)方法字节码载入方法区
5)main 线程开始运行,分配栈帧内存
(stack=2,locals=4)
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等)
- 传递参数,执行新栈帧中的字节码
- 执行完毕,弹出栈帧
- 清除 main 操作数栈内容
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;
}
反编译后的字节码:
编译器会按(源码中)从上至下的顺序,收集所有 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);
}
}
下面是反编译后的字节码:
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"
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后。