程序计数器的部分疑问

jvm中有一个比较重要的就是程序计数器,简单记录一下。
程序计数器(Program Counter Register,PC 寄存器) 记录的字节码指令地址是 在编译阶段确认好的,但在运行阶段会根据程序的执行流程动态变化。

1. 字节码指令的存储位置

1.1 字节码指令的来源

编译阶段:
Java 源代码在编译阶段会被编译成字节码(.class 文件)。
字节码是一种中间代码,与平台无关,由 JVM 解释执行或即时编译(JIT)为机器码。

运行阶段:
JVM 加载 .class 文件,将字节码指令加载到 方法区(Method Area) 中。
字节码指令在方法区中以方法为单位存储。

1.2 字节码指令的存储位置

方法区(Method Area):
字节码指令存储在方法区中,每个方法对应一段字节码指令。
方法区是所有线程共享的内存区域。
运行时常量池(Runtime Constant Pool):
字节码指令中可能包含对常量池的引用(如字符串常量、类名、方法名等)。
运行时常量池是方法区的一部分。

2. 程序计数器的作用

2.1 程序计数器的功能

记录执行位置:
程序计数器记录当前线程正在执行的字节码指令地址。
线程切换恢复:
在线程切换时,程序计数器保存当前线程的执行位置,以便恢复执行。
分支、循环、异常处理:
在分支、循环和异常处理时,程序计数器会跳转到指定的字节码地址。

2.2 程序计数器的内容

字节码指令地址:
程序计数器存储的是字节码指令的地址,而不是字节码指令本身。
字节码指令地址是相对于方法起始地址的偏移量。

动态变化:
程序计数器的值在运行阶段会根据程序的执行流程动态变化。
例如,在方法调用、分支跳转、循环和异常处理时,程序计数器的值会更新。

3. 字节码指令地址的确认

3.1 编译阶段

字节码生成:
在编译阶段,Java 编译器将源代码编译成字节码,并生成 .class 文件。
字节码指令的地址在编译阶段已经确定,并以偏移量的形式存储在 .class 文件中。

常量池:
字节码指令中可能包含对常量池的引用(如方法名、类名、字符串常量等)。
常量池的索引在编译阶段已经确定。

3.2 运行阶段

字节码加载:
JVM 加载 .class 文件,将字节码指令加载到方法区中。
字节码指令的地址在方法区中是固定的。
程序计数器更新:
在运行阶段,程序计数器根据字节码指令的执行流程动态更新。
例如,在方法调用时,程序计数器跳转到被调用方法的字节码起始地址。

4. 示例

以下是一个简单的 Java 方法示例,展示字节码指令的生成和程序计数器的作用:

4.1 Java 代码

public class Example {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int result = add(a, b);
        System.out.println(result);
    }

    public static int add(int x, int y) {
        int sum = x + y;
        return sum;
    }
}

4.2 字节码指令

通过 javap -c Example 可以查看字节码指令:

public class Example {
  public static void main(java.lang.String[]);
    Code:
       0: iconst_1        // 将常量 1 压入操作数栈
       1: istore_1        // 将栈顶值存储到局部变量表索引 1(a)
       2: iconst_2        // 将常量 2 压入操作数栈
       3: istore_2        // 将栈顶值存储到局部变量表索引 2(b)
       4: iload_1         // 加载局部变量表索引 1 的值(a)
       5: iload_2         // 加载局部变量表索引 2 的值(b)
       6: invokestatic  #2 // 调用 add 方法
       9: istore_3        // 将栈顶值存储到局部变量表索引 3(result)
      10: getstatic     #3 // 获取 System.out 的引用
      13: iload_3         // 加载局部变量表索引 3 的值(result)
      14: invokevirtual #4 // 调用 println 方法
      17: return          // 返回

  public static int add(int, int);
    Code:
       0: iload_0         // 加载局部变量表索引 0 的值(x)
       1: iload_1         // 加载局部变量表索引 1 的值(y)
       2: iadd            // 将栈顶两个值相加
       3: istore_2        // 将栈顶值存储到局部变量表索引 2(sum)
       4: iload_2         // 加载局部变量表索引 2 的值(sum)
       5: ireturn         // 返回栈顶值
}

4.3 程序计数器的变化

main 方法执行:
程序计数器从 0 开始,逐条执行字节码指令。
在调用 add 方法时,程序计数器跳转到 add 方法的字节码起始地址。
add 方法执行:
程序计数器从 0 开始,逐条执行 add 方法的字节码指令。
在 add 方法返回时,程序计数器跳转回 main 方法的下一条指令地址。

5. 总结

程序计数器是在编译成字节码的时候确认的,意味着在运行态是不可变的,反馈到底层上面,就是一堆寄存器汇编指令,进行方法的跳转,偏移以及赋值,其中的内存地址在发生变化而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值