OpenJDK17.0.8字节码解读样例
因为JDK17将会成为未来5至10年里Java应用的主流JDK,刚好闲着没事,就想着将《深入理解Java虚拟机》一书中关于字节码的解读样例在OpenJDK17.0.8上看看变化有多大!
先把实验环境说明一下:
OS:Windows 10 专业版 22H2
JDK:openjdk version "17.0.8" 2023-07-18 LTS
源码如下:
1 package org.example.chapter1; 2 3 public class TestClass { 4 private int m; 5 6 public int inc() { 7 int x; 8 try { 9 x = 1; 10 return x; 11 } catch (Exception e) { 12 x = 2; 13 return x; 14 } finally { 15 x = 3; 16 } 17 } 18 }
使用JDK自带的工具 javac 编译后,再用JDK自带的工具 javap 对编译出来的 *.class 文件解析出来的字节码如下(添加了说明):
#使用的 javap 命令和选项如下 javap -verbose -l org.example.chapter1.TestClass
Classfile /E:/Projects/demo001/src/main/java/org/example/chapter1/TestClass.class Last modified 2023年8月26日; size 457 bytes SHA-256 checksum 93dd8cecee43cd228708aed7079cc1b41502cb2279b365cdbf59e74e8c8c057f Compiled from "TestClass.java" public class org.example.chapter1.TestClass minor version: 0 //Class文件【次版本号】 major version: 61 //Class文件【主版本号】 flags: (0x0021) ACC_PUBLIC, ACC_SUPER //访问标志组合 this_class: #9 // org/example/chapter1/TestClass super_class: #2 // java/lang/Object interfaces: 0, fields: 1, methods: 2, attributes: 1 Constant pool: //常量池 #1 = Methodref #2.#3 // java/lang/Object."<init>":()V #2 = Class #4 // java/lang/Object #3 = NameAndType #5:#6 // "<init>":()V #4 = Utf8 java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = Class #8 // java/lang/Exception #8 = Utf8 java/lang/Exception #9 = Class #10 // org/example/chapter1/TestClass #10 = Utf8 org/example/chapter1/TestClass #11 = Utf8 m #12 = Utf8 I #13 = Utf8 Code #14 = Utf8 LineNumberTable #15 = Utf8 inc #16 = Utf8 ()I #17 = Utf8 StackMapTable #18 = Class #19 // java/lang/Throwable #19 = Utf8 java/lang/Throwable #20 = Utf8 SourceFile #21 = Utf8 TestClass.java { public org.example.chapter1.TestClass(); descriptor: ()V flags: (0x0001) 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 3: 0 public int inc(); descriptor: ()I flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=5, args_size=1 0: iconst_1 //将 int 型 1 推送至栈顶(进入try代码块) 1: istore_1 //将栈顶 int 型数值存入第二个本地变量(对应try代码块里的 x=1;) 2: iload_1 //将第二个 int 型本地变量推送至栈顶(x压入栈顶,此时x=1) 3: istore_2 //将栈顶 int 型数值存入第三个本地变量(将栈顶的x存入该三号隐式本地变量中暂存) 4: iconst_3 //将 int 型 3 推送至栈顶(进入finally代码块) 5: istore_1 //将栈顶 int 型数值存入第二个本地变量(对应finally代码块里的 x=3;) 6: iload_2 //将第三个 int 型本地变量推送至栈顶(三号隐式本地变量中暂存的x=1压入栈顶) 7: ireturn //将栈顶 int 型数值作为返回值返回(即返回了栈顶的x=1,方法未出现异常的情况执行结束!) 8: astore_2 //将栈顶 引用 型数值存入第三个本地变量(栈顶当前存放的是某个显式异常实例对象的引用,到达catch代码块,复用了三号隐式本地变量,之后就没有对该变量做任何操作了,因为异常实例e没有在后续代码中使用) 9: iconst_2 //将 int 型 2 推送至栈顶(进入catch代码块) 10: istore_1 //将栈顶 int 型数值存入第二个本地变量(对应catch代码块里的 x=2;) 11: iload_1 //将第二个 int 型本地变量推送至栈顶(x压入栈顶,此时x=2) 12: istore_3 //将栈顶 int 型数值存入第四个本地变量(将栈顶的x存入该四号隐式本地变量中暂存) 13: iconst_3 //将 int 型 3 推送至栈顶(进入finally代码块) 14: istore_1 //将栈顶 int 型数值存入第二个本地变量(对应finally代码块里的 x=3;) 15: iload_3 //将第四个 int 型本地变量推送至栈顶(四号隐式本地变量中暂存的x=2压入栈顶) 16: ireturn //将栈顶 int 型数值作为返回值返回(即返回了栈顶的x=2,方法出现显式异常的情况执行结束!) 17: astore 4 //将栈顶 引用 型数值存入第五个本地变量(将栈顶的某个隐式异常实例对象的引用存入该五号隐式本地变量中暂存,出现显式代码无法捕获的其他异常) 19: iconst_3 //将 int 型 3 推送至栈顶(进入finally代码块) 20: istore_1 //将栈顶 int 型数值存入第二个本地变量(对应finally代码块里的 x=3;) 21: aload 4 //将第五个 引用 型本地变量推送至栈顶(五号隐式本地变量中暂存的某个隐式异常实例对象的引用存入栈顶) 23: athrow //将栈顶的异常抛出 Exception table: //【异常表】 from to target type 0 4 8 Class java/lang/Exception 0 4 17 any 8 13 17 any 17 19 17 any LineNumberTable: //【行号表】 line 9: 0 line 10: 2 line 13: 15 line 15: 17 line 16: 21 StackMapTable: number_of_entries = 2 //栈映射表(也叫栈图、栈图表) frame_type = 72 /* same_locals_1_stack_item */ stack = [ class java/lang/Exception ] frame_type = 72 /* same_locals_1_stack_item */ stack = [ class java/lang/Throwable ] } SourceFile: "TestClass.java"
OpenJDK17.0.8中生成的字节码Code属性的内容与JDK8之前有些变化,不过还是5个局部变量(locals=5),因为 inc() 是实例方法,所以第一个局部变量分配给了指向当前实例的this用!第二个局部变量分配给了显式变量x用,其他变量见Code属性字节码指令的说明!