一. 概述
1.1 字节码文件的跨平台性
1.2 java的前端编译器
AoT 在代码执行之前就将代码编译成机器指令,运行效率稍微高点,但是就牺牲了java 的动态运行的特性, 影响了一次编译到处运行
1.3 透过字节码指令看代码细节
代码举例1
java代码
public class IntegerTest {
public static void main(String[] args) {
Integer x = 5;
int y = 5;
// true
System.out.println(x == y);
Integer i1 = 10;
Integer i2 = 10;
// true
System.out.println(i1 == i2);
Integer i3 = 128;
Integer i4 = 128;
// false
System.out.println(i3 == i4);
}
}
字节码文件
1行 --> 调用调用了valueof 方法, 实际是首先初始化了一个256大小(byte) 的new Integer()[], 然后进行下面的判断
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
如果数据是在-128 - 127之间的话是直接从数组中取出数据的,没有新new对象
0 iconst_5 # 将5 放入操作数栈中
1 invokestatic #2 <java/lang/Integer.valueOf> # 调用valueOf()方法
4 astore_1 # 取出栈中的数据 5 放到局部变量表位置为1(x变量) 的地方, 0的位置放的是args变量
5 iconst_5 # 将5放入操作数栈中
6 istore_2 # 将栈中的5取出放入局部变量表为2(y变量)的地方
7 getstatic #3 <java/lang/System.out>
10 aload_1
11 invokevirtual #4 <java/lang/Integer.intValue>
14 iload_2
15 if_icmpne 22 (+7)
18 iconst_1
19 goto 23 (+4)
22 iconst_0
23 invokevirtual #5 <java/io/PrintStream.println>
26 bipush 10
28 invokestatic #2 <java/lang/Integer.valueOf>
31 astore_3
32 bipush 10
34 invokestatic #2 <java/lang/Integer.valueOf>
37 astore 4
39 getstatic #3 <java/lang/System.out>
42 aload_3
43 aload 4
45 if_acmpne 52 (+7)
48 iconst_1
49 goto 53 (+4)
52 iconst_0
53 invokevirtual #5 <java/io/PrintStream.println>
56 sipush 128
59 invokestatic #2 <java/lang/Integer.valueOf>
62 astore 5
64 sipush 128
67 invokestatic #2 <java/lang/Integer.valueOf>
70 astore 6
72 getstatic #3 <java/lang/System.out>
75 aload 5
77 aload 6
79 if_acmpne 86 (+7)
82 iconst_1
83 goto 87 (+4)
86 iconst_0
87 invokevirtual #5 <java/io/PrintStream.println>
90 return
代码举例2
java 代码
public class StringTest {
public static void main(String[] args) {
String str = new String("hello") + new String("World");
// str.intern();
String str1 = "helloWorld";
// false // 在jdk8.0如果调用了intern()方法,则变成了true
System.out.println(str == str1);
}
}
字节码
0 new #2 <java/lang/StringBuilder>
3 dup
4 invokespecial #3 <java/lang/StringBuilder.<init>>
7 new #4 <java/lang/String>
10 dup
11 ldc #5 <hello>
13 invokespecial #6 <java/lang/String.<init>>
16 invokevirtual #7 <java/lang/StringBuilder.append>
19 new #4 <java/lang/String>
22 dup
23 ldc #8 <World>
25 invokespecial #6 <java/lang/String.<init>>
28 invokevirtual #7 <java/lang/StringBuilder.append>
31 invokevirtual #9 <java/lang/StringBuilder.toString>
34 astore_1
35 ldc #10 <helloWorld>
37 astore_2
38 getstatic #11 <java/lang/System.out>
41 aload_1
42 aload_2
43 if_acmpne 50 (+7)
46 iconst_1
47 goto 51 (+4)
50 iconst_0
51 invokevirtual #12 <java/io/PrintStream.println>
54 return
代码举例3
成员变量(非静态的) 的赋值过程: 1> 默认初始化 2> 显示初始化 / 代码块中初始化 3> 构造器中初始化 4> 有了对象之后, 可"对象.属性"或者"对象.方法"初始化
Java代码
class Father {
int x = 10;
public Father() {
this.print();
x = 20;
}
public void print() {
System.out.println("Father.x = " + x);
}
}
class Son extends Father {
int x = 30;
public Son() {
this.print();
x = 40;
}
@Override
public void print() {
System.out.println("Son.x = " + x);
}
}
public class SonTest {
public static void main(String[] args) {
Father f = new Son();
System.out.println(f.x);
}
/**
* Son.x = 0
* Son.x = 30
* 20
*/
}
首先打印Son.x = 0 的原因是在调son的构造函数之前首先调用了父类的构造,在父类的构造里面调用了this.print, 因为子类重写了父类的print方法,这个时候实际调用的是子类的print, 其实这个时候子类中的x并没有被初始化为30,还是为0, 所以打印的是一个0
然后是调用子类构造方法,此时子类中的x已经被初始化成了30, 所以打印Son.x = 30
然后是父类.属性,由于属性并不存在多态性, 所以打印的是父类自己的属性 20
字节码
main
0 new #2 <demo6/Son>
3 dup
4 invokespecial #3 <demo6/Son.<init>>
7 astore_1
8 getstatic #4 <java/lang/System.out>
11 aload_1
12 getfield #5 <demo6/Father.x>
15 invokevirtual #6 <java/io/PrintStream.println>
18 return
son
0 aload_0
1 invokespecial #1 <demo6/Father.<init>>
4 aload_0
5 bipush 30
7 putfield #2 <demo6/Son.x>
10 aload_0
11 invokevirtual #3 <demo6/Son.print>
14 aload_0
15 bipush 40
17 putfield #2 <demo6/Son.x>
20 return
father
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 aload_0
5 bipush 10
7 putfield #2 <demo6/Father.x>
10 aload_0
11 invokevirtual #3 <demo6/Father.print>
14 aload_0
15 bipush 20
17 putfield #2 <demo6/Father.x>
20 return
二. 虚拟机的基石 : Class文件
三.Class文件结构
可以把表理解为数组结构
示例:
代码
public class Demo {
private int num = 1;
public int add() {
num = num + 2;
return num;
}
}
反编译得到的代码
package demo7;
public class Demo {
private int num = 1;
public Demo() {
}
public int add() {
this.num += 2;
return this.num;
}
}
字节码文件
// class version 52.0 (52)
// access flags 0x21
public class demo7/Demo {
// compiled from: Demo.java
// access flags 0x2
private I num
// access flags 0x1
public <init>()V
L0
LINENUMBER 11 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 12 L1
ALOAD 0
ICONST_1
PUTFIELD demo7/Demo.num : I
RETURN
L2
LOCALVARIABLE this Ldemo7/Demo; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
// access flags 0x1
public add()I
L0
LINENUMBER 15 L0
ALOAD 0
ALOAD 0
GETFIELD demo7/Demo.num : I
ICONST_2
IADD
PUTFIELD demo7/Demo.num : I
L1
LINENUMBER 16 L1
ALOAD 0
GETFIELD demo7/Demo.num : I
IRETURN
L2
LOCALVARIABLE this Ldemo7/Demo; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
3.1 魔数 : Class文件的标志
3.2 Class文件版本号
3.3 常量池: 存放所有常量
运行时常量池中存放的是各种字面量和符号引用
3.3.1 常量池计数器
3.3.2 常量池表
3.3.2.1 字面量和符号引用
注: 此时数据并未在内存中,只是存在于class字节码文件中
3.3.2.2 常量类型和结构
可结合前面的excel文件查看解析结果
3.4 访问标识
3.5 类索引, 父类索引 ,接口索引集合
3.6 字段(field)表集合
3.6.1 字段计数器
3.6.2 字段表
3.7 方法表集合
3.7.1 方法计数器
3.7.2 方法表
3.8 属性表集合
3.8.1 属性计数器
3.8.2 属性表
具体参看, 纯字节码的看看得了
3.9 小结
四.使用Javap指令解析Class文件
4.1解析字节码的作用
4.2javac -g 操作
4.3 javap的用法
4.4 使用举例
java
public class JavapTest {
private int num;
boolean flag;
protected char gender;
public String info;
public static final int COUNTS = 1;
static {
String url = "www.huqi.com";
}
{
info = "java";
}
public JavapTest() {
}
private JavapTest(boolean flag) {
this.flag = flag;
}
private void methodPrivate() {
}
int getNum(int i){
return num + i;
}
protected char showGender() {
return gender;
}
public void showInfo() {
int i = 10;
System.out.println(info + i);
}
}
字节码
javap -v -p JavapTest.class 拿到所有的完整的信息
Classfile /D:/code/demo/JVMDemo/ch03/src/main/java/demo7/JavapTest.class // 字节码文件所属的路径
Last modified 2021-5-11; size 1331 bytes // 最后修改时间, 字节码文件大小
MD5 checksum 45db889d416b756de5fa084e44259081 // MD5散列值
Compiled from "JavapTest.java" // 源文件的名称
public class demo7.JavapTest
minor version: 0 // 副版本
major version: 52 // 主版本
flags: ACC_PUBLIC, ACC_SUPER // 访问标识. ACC_SUPER代表这是一个类, public代表是公开的
Constant pool: // 常量池
#1 = Methodref #16.#46 // java/lang/Object."<init>":()V
#2 = String #47 // java
#3 = Fieldref #15.#48 // demo7/JavapTest.info:Ljava/lang/String;
#4 = Fieldref #15.#49 // demo7/JavapTest.flag:Z
#5 = Fieldref #15.#50 // demo7/JavapTest.num:I
#6 = Fieldref #15.#51 // demo7/JavapTest.gender:C
#7 = Fieldref #52.#53 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #54 // java/lang/StringBuilder
#9 = Methodref #8.#46 // java/lang/StringBuilder."<init>":()V
#10 = Methodref #8.#55 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#11 = Methodref #8.#56 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#12 = Methodref #8.#57 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#13 = Methodref #58.#59 // java/io/PrintStream.println:(Ljava/lang/String;)V
#14 = String #60 // www.huqi.com
#15 = Class #61 // demo7/JavapTest
#16 = Class #62 // java/lang/Object
#17 = Utf8 num
#18 = Utf8 I
#19 = Utf8 flag
#20 = Utf8 Z
#21 = Utf8 gender
#22 = Utf8 C
#23 = Utf8 info
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 COUNTS
#26 = Utf8 ConstantValue
#27 = Integer 1
#28 = Utf8 <init>
#29 = Utf8 ()V
#30 = Utf8 Code
#31 = Utf8 LineNumberTable
#32 = Utf8 LocalVariableTable
#33 = Utf8 this
#34 = Utf8 Ldemo7/JavapTest;
#35 = Utf8 (Z)V
#36 = Utf8 methodPrivate
#37 = Utf8 getNum
#38 = Utf8 (I)I
#39 = Utf8 i
#40 = Utf8 showGender
#41 = Utf8 ()C
#42 = Utf8 showInfo
#43 = Utf8 <clinit>
#44 = Utf8 SourceFile
#45 = Utf8 JavapTest.java
#46 = NameAndType #28:#29 // "<init>":()V
#47 = Utf8 java
#48 = NameAndType #23:#24 // info:Ljava/lang/String;
#49 = NameAndType #19:#20 // flag:Z
#50 = NameAndType #17:#18 // num:I
#51 = NameAndType #21:#22 // gender:C
#52 = Class #63 // java/lang/System
#53 = NameAndType #64:#65 // out:Ljava/io/PrintStream;
#54 = Utf8 java/lang/StringBuilder
#55 = NameAndType #66:#67 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#56 = NameAndType #66:#68 // append:(I)Ljava/lang/StringBuilder;
#57 = NameAndType #69:#70 // toString:()Ljava/lang/String;
#58 = Class #71 // java/io/PrintStream
#59 = NameAndType #72:#73 // println:(Ljava/lang/String;)V
#60 = Utf8 www.huqi.com
#61 = Utf8 demo7/JavapTest
#62 = Utf8 java/lang/Object
#63 = Utf8 java/lang/System
#64 = Utf8 out
#65 = Utf8 Ljava/io/PrintStream;
#66 = Utf8 append
#67 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#68 = Utf8 (I)Ljava/lang/StringBuilder;
#69 = Utf8 toString
#70 = Utf8 ()Ljava/lang/String;
#71 = Utf8 java/io/PrintStream
#72 = Utf8 println
#73 = Utf8 (Ljava/lang/String;)V
################################# 字段表集合的信息 ####################################################################
{ // 字段表集合的信息
private int num; // 字段名
descriptor: I // 字段描述符: 字段的类型
flags: ACC_PRIVATE // 字段的访问标识
boolean flag;
descriptor: Z
flags:
protected char gender;
descriptor: C
flags: ACC_PROTECTED
public java.lang.String info;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
public static final int COUNTS;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 1 // 常量字段的属性: ConstantValue
################################### 方法表集合的信息 ##################################################################
public demo7.JavapTest(); // 构造器1的信息
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: ldc #2 // String java
7: putfield #3 // Field info:Ljava/lang/String;
10: return
LineNumberTable:
line 21: 0
line 19: 4
line 23: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Ldemo7/JavapTest;
private demo7.JavapTest(boolean); // 构造器2的信息
descriptor: (Z)V
flags: ACC_PRIVATE
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String java
7: putfield #3 // Field info:Ljava/lang/String;
10: aload_0
11: iload_1
12: putfield #4 // Field flag:Z
15: return
LineNumberTable:
line 24: 0
line 19: 4
line 25: 10
line 26: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this Ldemo7/JavapTest;
0 16 1 flag Z
private void methodPrivate();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 29: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Ldemo7/JavapTest;
int getNum(int);
descriptor: (I)I
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: getfield #5 // Field num:I
4: iload_1
5: iadd
6: ireturn
LineNumberTable:
line 31: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Ldemo7/JavapTest;
0 7 1 i I
protected char showGender();
descriptor: ()C
flags: ACC_PROTECTED
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #6 // Field gender:C
4: ireturn
LineNumberTable:
line 34: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo7/JavapTest;
public void showInfo();
descriptor: ()V // 方法描述符: 方法的形参列表,返回值类型
flags: ACC_PUBLIC // 方法的访问标识
Code:
stack=3, locals=2, args_size=1 // stack: 操作数栈的最大深度 locals: 局部变量表的长度(this + 自己定义的一个) args_size: 方法接受参数的个数(无参的都为1,是this)
//偏移量 操作码 操作数
0: bipush 10
2: istore_1
3: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
6: new #8 // class java/lang/StringBuilder
9: dup
10: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V
13: aload_0
14: getfield #3 // Field info:Ljava/lang/String;
17: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: iload_1
21: invokevirtual #11 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
30: return
// 行号表: 指明字节码指令的偏移量于java源程序中代码的行号的一一对应关系
LineNumberTable:
line 37: 0 java代码中的行号(37) : 字节码指令中的行号(0)
line 38: 3
line 39: 30
// 局部变量表
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 this Ldemo7/JavapTest; 从0 - 31 行有效
3 28 1 i I
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=1, args_size=0
0: ldc #14 // String www.huqi.com
2: astore_0
3: return
LineNumberTable:
line 16: 0
line 17: 3
LocalVariableTable:
Start Length Slot Name Signature
}
SourceFile: "JavapTest.java" // 附加属性: 指明当前字节码文件对应的源程序文件名
4.5 总结