在最初接触jvm的几天里,我一直有这样的疑问,jvm a方法调用另一个b方法 当b方法返回后 jvm是怎么知道要返回到哪里的?
查阅了程序计数器的许多资料,都讲的不是很明白,对程序计数器都差不多是同样的描述文字.
后来在看到栈帧的介绍的时候突然看到了返回地址这么一个东西,感觉就和我的答案非常接近了,继续寻找答案的同时也顺便弄明白了程序计数器是怎么工作的.
每个线程有有许多栈帧,每个栈帧的结构如下图
栈帧结构图

方法返回地址
存放调用调用该方法的pc计数器的值。当一个方法开始之后,只有两种方式可以退出这个方法:1、执行引擎遇到任意一个方法返回的字节码指令,也就是所谓的正常完成出口。2、在方法执行的过程中遇到了异常,并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种方式成为异常完成出口。正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。
无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置,方法正常退出时,调用者的pc计数器的值作为返回地址,而通过异常退出的,返回地址是要通过异常处理器表来确定,栈帧中一般不会保存这部分信息。本质上,方法的退出就是当前栈帧出栈的过程。
也就是说每个线程都有一个程序计数器,当从a方法到b方法的时候,会产生一个新的栈帧,会将当前方法块内的执行位置(也就是程序计数器的值)存入到这个新的栈帧的返回地址内
代码
package com;
public class test2 {
public static void main(String args[]) {
int a = 1;
int b = a + 1;
test2 c = new test2();
int d = c.method1(a);
}
public int method1(int v1) {
v1 = v1 + 1;
v1 = method2(v1);
return v1;
}
public int method2(int v2) {
v2 = v2 + 1;
return v2;
}
}
反编译结果
G:\>javap -c test2.class
Compiled from "test2.java"
public class test2 {
public test2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: istore_1
2: iload_1
3: iconst_1
4: iadd
5: istore_2
6: new #2 // class test2
9: dup
10: invokespecial #3 // Method "<init>":()V
13: astore_3
14: aload_3
15: iload_1
16: invokevirtual #4 // Method method1:(I)I
19: istore 4
21: return
public int method1(int);
Code:
0: iload_1
1: iconst_1
2: iadd
3: istore_1
4: aload_0
5: iload_1
6: invokevirtual #5 // Method method2:(I)I
9: istore_1
10: iload_1
11: ireturn
public int method2(int);
Code:
0: iload_1
1: iconst_1
2: iadd
3: istore_1
4: iload_1
5: ireturn
}
主要参考
https://blog.csdn.net/dd864140130/article/details/49515403#commentBox
网友评论