虚拟机字节码执行

​ 最近对虚拟机的执行过程比较感兴趣,中午拿了一段《深入理解java虚拟机》的代码来跑了跑,最主要的还是在字节码层面虚拟机是如何执行的。

源代码

1
2
3
4
5
6
7
public static void main(String[]  args){
Map<String, String> map = new HashMap<String, String>();
Integer a = 122;
map.put("hello", "你好");
map.put("how are you","吃过饭了吗");
System.out.println(map.get("how are you"));
}

使用javap 查看class文件

javap -verbose test10d2.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
Picked up _JAVA_OPTIONS:   -Dawt.useSystemAAFontSettings=gasp
Classfile /media/blankspace/resource/javaProgram/JVM/src/cn/swpu/test/test10d2.class
Last modified Apr 1, 2019; size 808 bytes
MD5 checksum 04db935845801d51fd5a7533e286da1b
Compiled from "test10d2.java"
public class cn.swpu.test.test10d2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #15.#24 // java/lang/Object."<init>":()V
#2 = Class #25 // java/util/HashMap
#3 = Methodref #2.#24 // java/util/HashMap."<init>":()V
#4 = Methodref #26.#27 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#5 = String #28 // hello
#6 = String #29 // 你好
#7 = InterfaceMethodref #30.#31 // java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#8 = String #32 // how are you
#9 = String #33 // 吃过饭了吗
#10 = Fieldref #34.#35 // java/lang/System.out:Ljava/io/PrintStream;
#11 = InterfaceMethodref #30.#36 // java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
#12 = Class #37 // java/lang/String
#13 = Methodref #38.#39 // java/io/PrintStream.println:(Ljava/lang/String;)V
#14 = Class #40 // cn/swpu/test/test10d2
#15 = Class #41 // java/lang/Object
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 SourceFile
#23 = Utf8 test10d2.java
#24 = NameAndType #16:#17 // "<init>":()V
#25 = Utf8 java/util/HashMap
#26 = Class #42 // java/lang/Integer
#27 = NameAndType #43:#44 // valueOf:(I)Ljava/lang/Integer;
#28 = Utf8 hello
#29 = Utf8 你好
#30 = Class #45 // java/util/Map
#31 = NameAndType #46:#47 // put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#32 = Utf8 how are you
#33 = Utf8 吃过饭了吗
#34 = Class #48 // java/lang/System
#35 = NameAndType #49:#50 // out:Ljava/io/PrintStream;
#36 = NameAndType #51:#52 // get:(Ljava/lang/Object;)Ljava/lang/Object;
#37 = Utf8 java/lang/String
#38 = Class #53 // java/io/PrintStream
#39 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
#40 = Utf8 cn/swpu/test/test10d2
#41 = Utf8 java/lang/Object
#42 = Utf8 java/lang/Integer
#43 = Utf8 valueOf
#44 = Utf8 (I)Ljava/lang/Integer;
#45 = Utf8 java/util/Map
#46 = Utf8 put
#47 = Utf8 (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
#48 = Utf8 java/lang/System
#49 = Utf8 out
#50 = Utf8 Ljava/io/PrintStream;
#51 = Utf8 get
#52 = Utf8 (Ljava/lang/Object;)Ljava/lang/Object;
#53 = Utf8 java/io/PrintStream
#54 = Utf8 println
#55 = Utf8 (Ljava/lang/String;)V
{
public cn.swpu.test.test10d2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class java/util/HashMap
3: dup
4: invokespecial #3 // Method java/util/HashMap."<init>":()V
7: astore_1
8: bipush 122
10: invokestatic #4 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: astore_2
14: aload_1
15: ldc #5 // String hello
17: ldc #6 // String 你好
19: invokeinterface #7, 3 // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
24: pop
25: aload_1
26: ldc #8 // String how are you
28: ldc #9 // String 吃过饭了吗
30: invokeinterface #7, 3 // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
35: pop
36: getstatic #10 // Field java/lang/System.out:Ljava/io/PrintStream;
39: aload_1
40: ldc #8 // String how are you
42: invokeinterface #11, 2 // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
47: checkcast #12 // class java/lang/String
50: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
53: return
LineNumberTable:
line 8: 0
line 9: 8
line 10: 14
line 11: 25
line 12: 36
line 13: 53
}
SourceFile: "test10d2.java"

验证泛型擦除

证明编译器在吧泛型类型擦除的代码19: invokeinterface #7, 3 // InterfaceMethod java/util/Map.put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

​ 与这句字节码对应的是 map.put(“hello”, “你好”); 此处invokeinterface引用了HashMap的父类的Map的put方法符号引用,根据方法描述符((Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object)可知,

所调用方法的参数和返回值都是Object类型的。

​ 书上说,java这么实现其实是一种伪泛型。在字节码层面都是把泛型类型当做Object来处理的。反编译后是有强制转换的java代码的。可是强制类型转换的字节码在哪呢?来看看偏移量为19的附近的字节码

虚拟机在调用完invokeinterface后,直接把map引用pop出站了!! 可见字节码并没有帮我们做强制转换啊…

字节码执行

​ 虚拟机把字节码从class文件加载到内存中,这个过程叫类加载,还要完成一系列复杂的检验过程(不检验的话,有高手根据class文件格式,修改了class文件咋办,所以还是得校验吧)。有关类加载的细节后面再来讨论,今天的重点是字节码的执行过程。

### 字节码的执行有两块重要的空间需要认识
  • JVM虚拟机栈

    这个虚拟机栈是每个线程独立的,意思就是说每个线程都有一个虚拟机栈。虚拟机栈是由栈帧组成的。

    一个方法的开始与结束,意味着一个栈帧的入栈和出栈。

    栈帧一般来说又包含了

    • 局部变量表
    • 操作数栈
    • 动态链接
    • 方法返回值
  • 一般来说对象的分配都是在这里完成的。但是编译器通过逃逸分析,有可能把对象分配到栈上。

局部变量表

在上面的例子中偏移量为7的字节码指令 astore_1

就是把新创建的map对象的引用,从操作数栈上,存储到局部变量表的第一个SLOT(局部变量表是以SLOT为单位的,一个SLOT一般是32位 4个字节)上。astore_0 是 存放的this变量。

操作数栈

15: ldc #5 // String hello
17: ldc #6 // String 你好

ldc 是把#5,#6字符串引用push到操作数栈上,此处起到传参的作用