今天学习一个jdk工具:javap,可以查看编译后的class字节码文件。
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 用法: javap <options> <classes> 其中, 可能的选项包括: -help --help -? 输出此用法消息 -version 版本信息 -v -verbose 输出附加信息 -l 输出行号和本地变量表 -public 仅显示公共类和成员 -protected 显示受保护的/公共类和成员 -package 显示程序包/受保护的/公共类 和成员 (默认) -p -private 显示所有类和成员 -c 对代码进行反汇编 -s 输出内部类型签名 -sysinfo 显示正在处理的类的 系统信息 (路径, 大小, 日期, MD5 散列) -constants 显示最终常量 -classpath <path> 指定查找用户类文件的位置 -cp <path> 指定查找用户类文件的位置 -bootclasspath <path> 覆盖引导类文件的位置
|
测试
1 2 3 4 5 6 7 8 9 10
| public class Test{ public static void main(String[] args) { int i = 1; String s1 = "a"; String s2 = "b"; String s3 = "a"+"b"; String s4 = "ab"; String s5 = s1 + s2; } }
|
编译后执行命令
输出:
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
| Classfile /E:/Test.class Last modified 2018-8-27; size 480 bytes MD5 checksum 5fe4ad86b37f7755fe4e1b52c67b953b Compiled from "Test.java" public class Test minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #10.#19 #2 = String #20 #3 = String #21 #4 = String #22 #5 = Class #23 #6 = Methodref #5.#19 #7 = Methodref #5.#24 #8 = Methodref #5.#25 #9 = Class #26 #10 = Class #27 #11 = Utf8 <init> #12 = Utf8 ()V #13 = Utf8 Code #14 = Utf8 LineNumberTable #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 SourceFile #18 = Utf8 Test.java #19 = NameAndType #11:#12 #20 = Utf8 a #21 = Utf8 b #22 = Utf8 ab #23 = Utf8 java/lang/StringBuilder #24 = NameAndType #28:#29 #25 = NameAndType #30:#31 #26 = Utf8 Test #27 = Utf8 java/lang/Object #28 = Utf8 append #29 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #30 = Utf8 toString #31 = Utf8 ()Ljava/lang/String; { public Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 4: return LineNumberTable: line 1: 0
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=7, args_size=1 0: iconst_1 1: istore_1 2: ldc #2 4: astore_2 5: ldc #3 7: astore_3 8: ldc #4 10: astore 4 12: ldc #4 14: astore 5 16: new #5 19: dup 20: invokespecial #6 23: aload_2 24: invokevirtual #7 27: aload_3 28: invokevirtual #7 31: invokevirtual #8 34: astore 6 36: return LineNumberTable: line 3: 0 line 4: 2 line 5: 5 line 6: 8 line 7: 12 line 8: 16 line 9: 36 } SourceFile: "Test.java"
|
字节码文件结构
- class文件路径,最后修改时间,文件大小
- md5校验码。
- 类全路径
- 类描述信息
- 常量池
- 方法描述信息
下面看下字节执行步骤:
序号 |
助记符 |
含义 |
对应代码 |
0 |
iconst_1 |
把int类型1推至栈顶 |
|
1 |
istore_1 |
将栈顶int类型数值存入第二个本地变量 |
|
2 |
ldc #2 |
将String类型常量值从常量池中推送至栈顶 |
String a |
4 |
astore_2 |
将栈顶引用类型数值存入第三个本地变量 |
|
5 |
ldc #3 |
将String类型常量值从常量池中推送至栈顶 |
String b |
7 |
astore_3 |
将栈顶引用类型数值存入第四个本地变量 |
|
8 |
ldc #4 |
将String类型常量值从常量池中推送至栈顶 |
String ab |
10 |
astore 4 |
将引用类型数值存入第五个本地变量 |
|
12 |
ldc #4 |
将引用类型数值存入第五个本地变量 |
String ab |
14 |
astore 5 |
将引用类型数值存入第五个本地变量 |
|
16 |
new #5 |
创建一个对象,并将其引用值压入栈顶 |
class java/lang/StringBuilder |
19 |
dup |
复制栈顶数值并将数值值压入栈顶 |
|
20 |
invokespecial #6 |
调用超类构造方法,示例初始化方法,私有方法 |
Method java/lang/StringBuilder.”“:()V |
23 |
aload_2 |
将第三个引用类型本地变量推送至栈顶 |
|
24 |
invokevirtual #7 |
调用实例方法 |
Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; |
27 |
aload_3 |
将第四个引用类型本地变量推送至栈顶 |
|
28 |
invokevirtual #7 |
调用实例方法 |
Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; |
31 |
invokevirtual #8 |
调用实例方法 |
Method java/lang/StringBuilder.toString:()Ljava/lang/String; |
34 |
astore 6 |
将引用类型数值存入第七个本地变量 |
|
36 |
return |
从当前方法返回void |
|
从上面字节码和执行步骤可以看出:
“1”、“a”、“b”、“ab”在类加载的时候已经被放入常量池。
s1、s2、s3、s4都是用的常量池中的引用。
s5是使用了StringBuilder,append了“a”和“b”,然后调用了toString()方法,最后将引用赋给s5。
可见s3==s4。s4!=s5
再看LineNumberTable
1 2 3 4 5 6 7 8
| LineNumberTable: line 3: 0 line 4: 2 line 5: 5 line 6: 8 line 7: 12 line 8: 16 line 9: 36
|
左边代表代码中的行号,右边代表字节码执行的序号。
参考: