JVM之符号引用和直接引用

javap

javap命令是jdk自带的一个反汇编的工具,可以把class文件反编译成汇编的命令,查看一些java执行的一些内部指令。
先看一下javap命令的用法。
基本用法:java 参数 class文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-help  --help  -?        输出此用法消息
-version 版本信息,其实是当前javap所在jdk的版本信息,不是class在哪个jdk下生成的。
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示静态最终常量
-classpath <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置

一般常用的就是java -p -c -l -v class
执行 javap -c -l -p -v JavaP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main;

public class JavaP {

public static void main(String[] args) {
System.out.println("123");
}

private int calc(int a, int b){
int c = add(a,b);
return c;
}

private int add(int a, int b){
int c = a + b;
return c;
}

}

输出如下信息

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
116
117
118
119
120
121
122
123
124
125
126
Classfile /Users/feidao/IdeaProjects/JavaBase/target/classes/main/JavaP.class
Last modified 2019-1-23; size 762 bytes
MD5 checksum 16e9efadcdef5ba25453e47c624e3d62
Compiled from "JavaP.java"
public class main.JavaP
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#28 // java/lang/Object."<init>":()V
#2 = Fieldref #29.#30 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #31 // 123
#4 = Methodref #32.#33 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Methodref #6.#34 // main/JavaP.add:(II)I
#6 = Class #35 // main/JavaP
#7 = Class #36 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Lmain/JavaP;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 calc
#20 = Utf8 (II)I
#21 = Utf8 a
#22 = Utf8 I
#23 = Utf8 b
#24 = Utf8 c
#25 = Utf8 add
#26 = Utf8 SourceFile
#27 = Utf8 JavaP.java
#28 = NameAndType #8:#9 // "<init>":()V
#29 = Class #37 // java/lang/System
#30 = NameAndType #38:#39 // out:Ljava/io/PrintStream;
#31 = Utf8 123
#32 = Class #40 // java/io/PrintStream
#33 = NameAndType #41:#42 // println:(Ljava/lang/String;)V
#34 = NameAndType #25:#20 // add:(II)I
#35 = Utf8 main/JavaP
#36 = Utf8 java/lang/Object
#37 = Utf8 java/lang/System
#38 = Utf8 out
#39 = Utf8 Ljava/io/PrintStream;
#40 = Utf8 java/io/PrintStream
#41 = Utf8 println
#42 = Utf8 (Ljava/lang/String;)V
{
public main.JavaP();
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
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lmain/JavaP;

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String 123
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 9: 0
line 10: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;

private int calc(int, int);
descriptor: (II)I
flags: ACC_PRIVATE
Code:
stack=3, locals=4, args_size=3
0: aload_0
1: iload_1
2: iload_2
3: invokespecial #5 // Method add:(II)I
6: istore_3
7: iload_3
8: ireturn
LineNumberTable:
line 13: 0
line 14: 7
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lmain/JavaP;
0 9 1 a I
0 9 2 b I
7 2 3 c I

private int add(int, int);
descriptor: (II)I
flags: ACC_PRIVATE
Code:
stack=2, locals=4, args_size=3
0: iload_1
1: iload_2
2: iadd
3: istore_3
4: iload_3
5: ireturn
LineNumberTable:
line 18: 0
line 19: 4
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lmain/JavaP;
0 6 1 a I
0 6 2 b I
4 2 3 c I
}
SourceFile: "JavaP.java"

符号引用

接着上文反汇编输出的信息分析什么是符号引用,以calc这个方法为例,可以看到

1
3: invokespecial #5                  // Method add:(II)I

这行就是调用add方法的指令,前面的invokespecial是指令,后面的#5是操作数,可以理解为调用了调用了哪个方法,#5代表的就是常量池中的第五项,也就是上面的这行

1
#5 = Methodref          #6.#34         // main/JavaP.add:(II)I

前面的Methodref是一种常量类型,后面的 #6.#34是常量池索引地址,#6又指向了#35,#35是一种utf8的字符串

1
#35 = Utf8               main/JavaP

#34指向了

1
#34 = NameAndType        #25:#20        // add:(II)I

一种叫NameAndType的常量类型,指向了#25:#20,#25是为

1
#25 = Utf8               add

#20为

1
#20 = Utf8               (II)I

所以把这个关系画出来就是

所以#2代表的就是 main/JavaP.add:(II)I,这个字符串就是所谓的符号引用,调用add方法的时候编译器就会把这行代码编译为

1
invokespecial main/JavaP.add:(II)I

但是这个并不是add方法的代码,靠这个字符串无法正常执行add方法,所以就需要找到add方法真的代码块,就是把符号引用解析为直接引用。

直接引用

直接引用就是方法(代码块)真正的入口,在解析的时候把符号引用的字符串在方法表中找到对应的入口,替代之前的符号引用,在上面的例子中就是把#5替代成方法的入口(#6.#34->methodblock*),这样在下次调用add方法的时候就不用再去解析#2,而是直接找到方法的入口。

总结

简单说符号引用就是常量池中的一个字符串,而直接引用是指向方法的真正的入口。
解析是类加载的一个过程,就是把符号引用解析为直接引用的过程,具体jvm是如何解析的我还没有去研究,了解了符号引用和直接引用就可以进一步了解jvm是如何执行一个方法的。

<参考>
https://www.zhihu.com/question/30300585/answer/51335493

Feidao wechat
关注我,一起打怪升级吧!