可以看到,反编译后的代码清单中, 有一个默认的构造函数 public
demo.jvm0104.HelloByteCode(), 以及 main 方法 。
刚学 Java 时我们就知道,如果不定义任何构造函数,就会有一个默认的无参构造函数,这里再次验证了这个知识点 。好吧,这比较容易理解!我们通过查看编译后的 class 文件证实了其中存在默认构造函数,所以这是 Java 编译器生成的,而不是运行时JVM自动生成的 。
自动生成的构造函数,其方法体应该是空的,但这里看到里面有一些指令 。为什么呢?
再次回顾 Java 知识, 每个构造函数中都会先调用 super 类的构造函数对吧? 但这不是 JVM 自动执行的, 而是由程序指令控制,所以默认构造函数中也就有一些字节码指令来干这个事情 。
基本上,这几条指令就是执行 super() 调用;
public demo.jvm0104.HelloByteCode();Code:0: aload_01: invokespecial #1// Method java/lang/Object."<init>":()V4: return
至于其中解析的 java/lang/Object 不用说, 默认继承了 Object 类 。这里再次验证了这个知识点,而且这是在编译期间就确定了的 。
继续往下看 c,
public static void main(java.lang.String[]);Code:0: new#2// class demo/jvm0104/HelloByteCode3: dup4: invokespecial #3// Method "<init>":()V7: astore_18: return
main 方法中创建了该类的一个实例,然后就 return 了,关于里面的几个指令,稍后讲解 。
4.4 查看 class 文件中的常量池信息
常量池 大家应该都听说过, 英文是 Constant pool 。这里做一个强调: 大多数时候指的是 运行时常量池 。但运行时常量池里面的常量是从哪里来的呢? 主要就是由 class 文件中的 常量池结构体 组成的 。
要查看常量池信息, 我们得加一点魔法参数:
javap -c -verbose demo.jvm0104.HelloByteCode
在反编译 class 时,指定 -verbose 选项, 则会 输出附加信息 。
结果如下所示:
Classfile /XXXXXXX/demo/jvm0104/HelloByteCode.classLast modified 2019-11-28; size 301 bytesMD5 checksum 542cb70faf8b2b512a023e1a8e6c1308Compiled from "HelloByteCode.java"public class demo.jvm0104.HelloByteCodeminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPERConstant pool:#1 = Methodref #4.#13 // java/lang/Object."<init>":()V#2 = Class #14 // demo/jvm0104/HelloByteCode#3 = Methodref #2.#13 // demo/jvm0104/HelloByteCode."<init>":()V#4 = Class #15 // java/lang/Object#5 = Utf8 <init>#6 = Utf8 ()V#7 = Utf8 Code#8 = Utf8 LineNumberTable#9 = Utf8 main#10 = Utf8 ([Ljava/lang/String;)V#11 = Utf8 SourceFile#12 = Utf8 HelloByteCode.java#13 = NameAndType #5:#6 // "<init>":()V#14 = Utf8 demo/jvm0104/HelloByteCode#15 = Utf8 java/lang/Object{public demo.jvm0104.HelloByteCode();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new #2 // class demo/jvm0104/HelloByteCode3: dup4: invokespecial #3 // Method "<init>":()V7: astore_18: returnLineNumberTable:line 5: 0line 6: 8}SourceFile: "HelloByteCode.java"
其中显示了很多关于 class 文件信息: 编译时间,MD5 校验和,从哪个 .java 源文件编译得来,符合哪个版本的 Java 语言规范等等 。
还可以看到 ACC_PUBLIC 和 ACC_SUPER 访问标志符 。ACC_PUBLIC 标志很容易理解:这个类是 public 类,因此用这个标志来表示 。
但 ACC_SUPER 标志是怎么回事呢? 这就是历史原因, JDK 1.0 的 BUG 修正中引入 ACC_SUPER 标志来修正 invokespecial 指令调用 super 类方法的问题,从 Java 1.1 开始,编译器一般都会自动生成ACC_SUPER 标志 。
有些同学可能注意到了,好多指令后面使用了 #1, #2, #3 这样的编号 。
这就是对常量池的引用 。那常量池里面有些什么呢?
Constant pool:#1 = Methodref #4.#13 // java/lang/Object."<init>":()V#2 = Class #14 // demo/jvm0104/HelloByteCode#3 = Methodref #2.#13 // demo/jvm0104/HelloByteCode."<init>":()V#4 = Class #15 // java/lang/Object#5 = Utf8 <init>......
这是摘取的一部分内容, 可以看到常量池中的常量定义 。还可以进行组合, 一个常量的定义中可以引用其他常量 。
比如第一行: #1 = Methodref #4.#13 // java/lang/Object."<init>":()V, 解读如下:
- #1 常量编号, 该文件中其他地方可以引用 。
- = 等号就是分隔符.
- Methodref 表明这个常量指向的是一个方法;具体是哪个类的哪个方法呢? 类指向的 #4, 方法签名指向的 #13; 当然双斜线注释后面已经解析出来可读性比较好的说明了 。
推荐阅读
- MyBatis自动生成工具,开发编码好帮手
- Python 常用的十行代码,建议收藏
- 绕过rar密码提取文件怎么操作?
- Java,OpenCV,图像阈值分割,阈值化,二值阈值化、截断阈值化等
- nest.js + sms 实现短信验证码登录
- 如何在开发中规避硬编码
- 十分钟搞懂手机号码一键登录
- 每天敲代码不到 1 小时,程序员每天都在做什么?
- 无代码系统搭建排班管理应用
- Java 日志记录—记录什么和不记录什么?