前言
学会看字节码指令有助于理解JVM底层代码执行逻辑。本文做一些核心知识的小记方便自己查看。
查看字节码工具和方法
可以用javap查看字节码内容。使用IDE的插件的话可以用jclasslib和java decompile等插件
1 2 3 4
| # javap -csvl Main.class
#
|
字节码指令结构
javap命令说明
javap命令可以用来查阅字节码文件,可以将指定的字节码文件反编译,反解析出当前类对应基本信息、常量池(Constant pool)、字段区域、方法区域(Code[JVM指令集])、异常表(Exception table)、本地变量表(LocalVariableTable)、行数表(LineNumberTable)和字节码操作数栈的映射表(StackMapTable)等信息。
- class文件的版本号:包括minor version和major version, major version是主版本号,55代表该Class文件利用JDK11编译,minor version为次版本号,低版本的class文件无法在新版本的JVM中运行。
- flags代表访问标识:Class类文件的访问标识通常右ACC_开头,ACC_PUBLIC, ACC_SUPER代表这个类是public的,具体信息可以参考JVM规范。
- Constant pool: 字节码的常量池是个静态常量池或者称之为类常量池,存放字面量和符号引用信息,按照索引组织
- 字段区域:展示字段描述符、访问权限(flag)信息
- 方法区域:
- 描述符和访问标志:
- stack=5,locals=3,args_size=3: 操作数栈、局部变量、参数的数量
- Code区:展示具体的字节码指令以及其关联的符号引用
- LineNumberTable:代码行数和指令索引之间的映射关系
- Exception Table: from to 限定了try catch的代码行范围,type定义了捕捉的异常类型
- LocalVariableTable: 局部变量表,start+length表示这个变量在字节码中的偏移位置
- StackMapTable: 栈图,用于提升jvm类型检查的验证过程效率。涉及类验证的过程。StackMapTable主要用来验证跳转前后locals、stack中的类型和大小一致。
指令说明
核心理念理解
字节码指令比较多,具体不罗列了,可以看参考资料[1]。记忆这些指令主要理解JVM本身是基于堆栈的内存模型,数的存取与计算是通过局部变量表、常量池、操作数栈配合运算指令来完成。
下图展示JAVA虚拟机栈在类加载整个流程中的位置。图参考[3]
下图展示JAVA虚拟机栈在main执行后和栈帧的关系(下图绿色部分是栈帧的构成)
下图展示栈帧字节码指令之间的细节
栈帧中通过动态链接(constant pool reference)指向运行时常量池方法的直接应用(主要针对动态分派的对象,例如多态的方法)。关联的方法是inovkedynamic指令
tips: 栈操作指令都有个前缀,标识其服务的数据类型,例如i,si,l都分别可以代表integer,small int,long等
方法调用指令
看字节码指令的时候,方法调用指令还是会经常看到,这边罗列下其功能。
- invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派), 这也是Java语言中最常见的方法分派方式。
- invokeinterface指令:用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。
- invokespecial指令:用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。
- invokestatic指令:用于调用类静态方法(static方法)。
- invokedynamic指令:用于在运行时动态解析出调用点限定符所引用的方法。并执行该方法。前面四条调用指令的分派逻辑都固化在Java虚拟机内部,用户无法改变,而invokedynamic指令的分派逻辑 是由用户所设定的引导方法决定的。
参考例子
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.kaimingwan.questions;
public class ByteCodeShow {
public static void main(String[] args) { int a = 5; long b = 3; long c = a + b; String result = "Result is "; System.out.println(result + c); }
}
|
javap字节码查看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| 警告: 二进制文件ByteCodeShow包含com.kaimingwan.questions.ByteCodeShow Classfile /Users/wanshao/projects/java-questions/target/classes/com/kaimingwan/questions/ByteCodeShow.class Last modified 2022-2-25; size 923 bytes MD5 checksum 8b5fdad13d0f32ec88a389856dca65c4 Compiled from "ByteCodeShow.java" public class com.kaimingwan.questions.ByteCodeShow minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #2 = Long 3l #4 = String #5 = Fieldref #6 = Class #7 = Methodref #8 = Methodref #9 = Methodref #10 = Methodref #11 = Methodref #12 = Class #13 = Class #14 = Utf8 <init> #15 = Utf8 ()V #16 = Utf8 Code #17 = Utf8 LineNumberTable #18 = Utf8 LocalVariableTable #19 = Utf8 this #20 = Utf8 Lcom/kaimingwan/questions/ByteCodeShow; #21 = Utf8 main #22 = Utf8 ([Ljava/lang/String;)V #23 = Utf8 args #24 = Utf8 [Ljava/lang/String; #25 = Utf8 a #26 = Utf8 I #27 = Utf8 b #28 = Utf8 J #29 = Utf8 c #30 = Utf8 result #31 = Utf8 Ljava/lang/String; #32 = Utf8 SourceFile #33 = Utf8 ByteCodeShow.java #34 = NameAndType #35 = Utf8 Result is #36 = Class #37 = NameAndType #38 = Utf8 java/lang/StringBuilder #39 = NameAndType #40 = NameAndType #41 = NameAndType #42 = Class #43 = NameAndType #44 = Utf8 com/kaimingwan/questions/ByteCodeShow #45 = Utf8 java/lang/Object #46 = Utf8 java/lang/System #47 = Utf8 out #48 = Utf8 Ljava/io/PrintStream; #49 = Utf8 append #50 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #51 = Utf8 (J)Ljava/lang/StringBuilder; #52 = Utf8 toString #53 = Utf8 ()Ljava/lang/String; #54 = Utf8 java/io/PrintStream #55 = Utf8 println #56 = Utf8 (Ljava/lang/String;)V { public com.kaimingwan.questions.ByteCodeShow(); 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 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/kaimingwan/questions/ByteCodeShow;
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=7, args_size=1 0: iconst_5 1: istore_1 2: ldc2_w #2 // long 3l 5: lstore_2 6: iload_1 7: i2l 8: lload_2 9: ladd 10: lstore 4 12: ldc #4 // String Result is 14: astore 6 16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 19: new #6 // class java/lang/StringBuilder 22: dup 23: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 26: aload 6 28: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: lload 4 33: invokevirtual #9 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder; 36: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 39: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 42: return LineNumberTable: line 9: 0 line 10: 2 line 11: 6 line 12: 12 line 13: 16 line 14: 42 LocalVariableTable: Start Length Slot Name Signature 0 43 0 args [Ljava/lang/String; 2 41 1 a I 6 37 2 b J 12 31 4 c J 16 27 6 result Ljava/lang/String; } SourceFile: "ByteCodeShow.java"
|
参考资料
- 【JVM进阶之路】十二:字节码指令
- Jvm系列3—字节码指令
- 从栈帧看字节码是如何在 JVM 中进行流转的
- The Java Virtual Machine Instruction Set
- javap - 查阅 Java 字节码