JVM动态链接(或指向运行时常量池的方法引用)
这里我用的是hotspot虚拟机
-
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如:invokedynamic指令
-
在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。
比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用**JVM栈帧内部结构**
虚拟机栈: -> 栈帧—对应每个方法----> 包含: 局部变量表, 本地方法栈, 动态链接, 方法出口,
有些地方会把方法返回地址,动态链接,一些附加信息叫做帧数据区
public class DynamicLinkingTest {
int num;
String info;
public void test1(){
info="JVM";
this.test2();
}
public void test2(){
num=2;
}
}
打开反编译后的字节码文件
public class com.qf.DynamicLinkingTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#23 // java/lang/Object."<init>":()V
#2 = String #24 // JVM
#3 = Fieldref #6.#25 // com/qf/DynamicLinkingTest.info:Ljava/lang/String;
#4 = Methodref #6.#26 // com/qf/DynamicLinkingTest.test2:()V
#5 = Fieldref #6.#27 // com/qf/DynamicLinkingTest.num:I
#6 = Class #28 // com/qf/DynamicLinkingTest
#7 = Class #29 // java/lang/Object
#8 = Utf8 num
#9 = Utf8 I
#10 = Utf8 info
#11 = Utf8 Ljava/lang/String;
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/qf/DynamicLinkingTest;
#19 = Utf8 test1
#20 = Utf8 test2
#21 = Utf8 SourceFile
#22 = Utf8 DynamicLinkingTest.java
#23 = NameAndType #12:#13 // "<init>":()V
#24 = Utf8 JVM
#25 = NameAndType #10:#11 // info:Ljava/lang/String;
#26 = NameAndType #20:#13 // test2:()V
#27 = NameAndType #8:#9 // num:I
#28 = Utf8 com/qf/DynamicLinkingTest
#29 = Utf8 java/lang/Object
{
int num;
descriptor: I
flags:
java.lang.String info;
descriptor: Ljava/lang/String;
flags:
public com.qf.DynamicLinkingTest();
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 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/qf/DynamicLinkingTest;
public void test1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: ldc #2 // String JVM
3: putfield #3 // Field info:Ljava/lang/String;
6: aload_0
7: invokevirtual #4 // Method test2:()V
10: return
LineNumberTable:
line 13: 0
line 14: 6
line 15: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/qf/DynamicLinkingTest;
public void test2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: iconst_2
2: putfield #5 // Field num:I
5: return
LineNumberTable:
line 17: 0
line 18: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/qf/DynamicLinkingTest;
}
SourceFile: "DynamicLinkingTest.java"
以Test1为例
我们可以看到 Code1里面有个#2
指向的是常量池里面的#2
我们定义的String类型赋值的JVM存储在这个位置
然后#2又指向了#24
这里的#xxx指向的都是常量池
当编译Java程序的时候,会得到程序中每一个类或者接口的独立的class文件。虽然独立看上去毫无关联,但是他们之间通过接口(harbor)符号互相联系,或者与Java API的class文件相联系。当运行程序的时候,Java虚拟机装载程序的类和接口,并且在动态连接的过程中把它们互相勾连起来。在程序运行中,Java虚拟机内部组织了一张互相连接的类和接口的网。
class把他们所有的引用符号放在一个地方——常量池。每一个class文件有一个常量池,每一个被Java虚拟机装载的类或者接口都有一份内部版本常量池,被称作运行时常量池。运行时常量池是特定与实现的数据结构,数据结构映射到class文件中的常量池。因此,当一个类型被首次装载的时候,所有来自于类型的符号引用都装载到了类型的运行时常量池。
在程序运行的过程中,如果某个特定的符号引用将要被使用,它首先要被解析。解析过程就是首先根据符号引用查找到实体,再把符号引用替换成直接引用的过程。因为所有的符号引用都是保存在常量池中,所以这种解析叫做常量池解析。