大神链接:作者有幸结识技术大神孙哥为好友,获益匪浅。现在把孙哥视频分享给大家。
孙哥链接:孙哥个人主页
作者简介:一个颜值99分,只比孙哥差一点的程序员
本专栏简介:话不多说,让我们一起干翻JVM本文章简介:话不多说,让我们讲清楚JVM当中与操作数栈相关的动态链接和常量池的作用
文章目录
知识回顾
1:栈帧中的结构图解
2:结构概念回顾
栈帧中的几部分大致可以分为这几个:局部变量表,操作数栈,动态链接,方法返回地址,一些附加信息。
局部变量表,操作数栈我们都已经详细的分析过了,接下来,我们该分享动态链接了。
值得一提的是:方法返回地址,动态链接和一些附加信息在有一些地方被称为帧数据区。这个概念主要在一些教材上是这样提到的,我们也需要知道。
一:动态链接
1:动态链接概念
动态链接又称为:执行运行时常量池的方法引用。
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如: invokedynamic指令
在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用 (Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
常量池就是我们的类被加载的时候所需要的信息,类、属性、方法的各种信息都会使用符号的形式声明出来,这是对类中各类数据的一个描述,而这个描述基于符号引用进行存储到了常量池这个区域。
而在栈帧中的字节码区域,字节码指令后边跟着一堆符号,这些符号就是常量池中的符号索引。这样就是所谓的:执行运行时常量池的方法引用。
常量池就是字节码整理后的那个区域,常量池当方法运行时会进入到方法区当中,此时的常量池就被称为运行时常量池。
动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
这一有一个问题,为什么要通过动态链接这种方式,而不是直接通过将数据直接放到引用的位置形成直接引用呢?
我们这样做的目的是为了保证相同数据只有一份,不同地方使用只需要添加引用即可。节约空间。提升运行效率。
为什么需要这个运行时常量池呢?运行时常量池就是把字节码中的常量池加载到了方法区中,没有这个运行时常量池也行,采用直接引用即可,但是这样就回到了上边的问题。
所以为什么需要常量池呢?
动态链接和常量池的作用,就是为了提供一些符号和常量,便于指令的识别。
2:编写代码证明
java
/**
* @author Administrator
*/
public class DynamicLInkingTest {
int num = 10;
public void methodA(){
System.out.println("methodA().....");
}
public void methodB(){
System.out.println("methodB().....");
methodA();
num++;
}
}
3:源代码的Javap
基于Javap对字节码进行整理,按照格式进行输出。
java
PS D:\code\study\hadoop\shit\target\classes> javap -v .\DynamicLInkingTest.class
Classfile /D:/code/study/hadoop/shit/target/classes/DynamicLInkingTest.class
Last modified 2023年11月12日; size 678 bytes
SHA-256 checksum acf0e245362759ceb374039c92493caf8913ff898697f7735b6ddedb262d2bd7
Compiled from "DynamicLInkingTest.java"
public class DynamicLInkingTest
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #8 // DynamicLInkingTest
super_class: #9 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #9.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #8.#24 // DynamicLInkingTest.num:I
#3 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #27 // methodA().....
#5 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = String #30 // methodB().....
#7 = Methodref #8.#31 // DynamicLInkingTest.methodA:()V
#8 = Class #32 // DynamicLInkingTest
#9 = Class #33 // java/lang/Object
#10 = Utf8 num
#11 = Utf8 I
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 LDynamicLInkingTest;
#19 = Utf8 methodA
#20 = Utf8 methodB
#21 = Utf8 SourceFile
#22 = Utf8 DynamicLInkingTest.java
#23 = NameAndType #12:#13 // "<init>":()V
#24 = NameAndType #10:#11 // num:I
#25 = Class #34 // java/lang/System
#26 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#27 = Utf8 methodA().....
#28 = Class #37 // java/io/PrintStream
#29 = NameAndType #38:#39 // println:(Ljava/lang/String;)V
#30 = Utf8 methodB().....
#31 = NameAndType #19:#13 // methodA:()V
#32 = Utf8 DynamicLInkingTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (Ljava/lang/String;)V
{
int num;
descriptor: I
flags: (0x0000)
public DynamicLInkingTest();
descriptor: ()V
flags: (0x0001) 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: bipush 10
7: putfield #2 // Field num:I
10: return
LineNumberTable:
line 4: 0
line 6: 4
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this LDynamicLInkingTest;
public void methodA();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String methodA().....
5: invokevirtual #5 // 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 this LDynamicLInkingTest;
public void methodB();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #6 // String methodB().....
5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: invokevirtual #7 // Method methodA:()V
12: aload_0
13: dup
14: getfield #2 // Field num:I
17: iconst_1
18: iadd
19: putfield #2 // Field num:I
22: return
LineNumberTable:
line 13: 0
line 15: 8
line 17: 12
line 18: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this LDynamicLInkingTest;
}
SourceFile: "DynamicLInkingTest.java"
我们关注一下methodB方法的内容:
我们截取其中的一行的字节码指令,3是地址,或者应该叫偏移地址。ldc是具体的偏移地址下的字节码指令。
java
3: ldc #6 // String methodB().....
二:常量池
1:常量池的概念
常量池就是我们的类被加载的时候所需要的信息,类、属性、方法的各种信息都会使用符号的形式声明出来,#1=Methodref后边可能继续是符号引用或者是真实的数据。总之跟着符号索引一定可以找到最终的真实数据,这是对类中各类数据的一个描述,而这个描述基于符号引用进行处处到了常量池这个区域。
而在栈帧中的字节码区域,字节码指令后边跟着一堆符号,这些符号就是常量池中的符号索引。这样就是所谓的:执行运行时常量池的方法引用。
java
Constant pool:
#1 = Methodref #9.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #8.#24 // DynamicLInkingTest.num:I
#3 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;
#4 = String #27 // methodA().....
#5 = Methodref #28.#29 // java/io/PrintStream.println:(Ljava/lang/String;)V
#6 = String #30 // methodB().....
#7 = Methodref #8.#31 // DynamicLInkingTest.methodA:()V
#8 = Class #32 // DynamicLInkingTest
#9 = Class #33 // java/lang/Object
#10 = Utf8 num
#11 = Utf8 I
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 LDynamicLInkingTest;
#19 = Utf8 methodA
#20 = Utf8 methodB
#21 = Utf8 SourceFile
#22 = Utf8 DynamicLInkingTest.java
#23 = NameAndType #12:#13 // "<init>":()V
#24 = NameAndType #10:#11 // num:I
#25 = Class #34 // java/lang/System
#26 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#27 = Utf8 methodA().....
#28 = Class #37 // java/io/PrintStream
#29 = NameAndType #38:#39 // println:(Ljava/lang/String;)V
#30 = Utf8 methodB().....
#31 = NameAndType #19:#13 // methodA:()V
#32 = Utf8 DynamicLInkingTest
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (Ljava/lang/String;)V
2:说明
当前我们只需要常量池对应了哪块区域即可,后边我们会详细的进行剖析。