同学们可以试着解析其他的常量定义 。自己实践加上知识回顾,能有效增加个人的记忆和理解 。
总结一下,常量池就是一个常量的大字典,使用编号的方式把程序里用到的各类常量统一管理起来,这样在字节码操作里,只需要引用编号即可 。
4.5 查看方法信息
在 javap 命令中使用 -verbose 选项时,还显示了其他的一些信息 。例如,关于 main 方法的更多信息被打印出来:
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=1
可以看到方法描述: ([Ljava/lang/String;)V:
- 其中小括号内是入参信息/形参信息;
- 左方括号表述数组;
- L 表示对象;
- 后面的java/lang/String就是类名称;
- 小括号后面的 V 则表示这个方法的返回值是 void;
- 方法的访问标志也很容易理解 flags: ACC_PUBLIC, ACC_STATIC,表示 public 和 static 。
public static void main(java.lang.String[]);稍微往回一点点,看编译器自动生成的无参构造函数字节码:
注:实际上我们一般把一个方法的修饰符+名称+参数类型清单+返回值类型,合在一起叫“方法签名”,即这些信息可以完整的表示一个方法 。
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: return
你会发现一个奇怪的地方, 无参构造函数的参数个数居然不是 0: stack=1, locals=1, args_size=1 。这是因为在 Java 中, 如果是静态方法则没有 this 引用 。对于非静态方法,this 将被分配到局部变量表的第 0 号槽位中, 关于局部变量表的细节,下面再进行介绍 。有反射编程经验的同学可能比较容易理解: Method#invoke(Object obj, Object... args); 有JavaScript编程经验的同学也可以类比: fn.Apply(obj, args) && fn.call(obj, arg1, arg2);4.6 线程栈与字节码执行模型
想要深入了解字节码技术,我们需要先对字节码的执行模型有所了解 。
JVM 是一台基于栈的计算机器 。每个线程都有一个独属于自己的线程栈(JVM stack),用于存储栈帧(Frame) 。每一次方法调用,JVM都会自动创建一个栈帧 。栈帧 由 操作数栈,局部变量数组 以及一个class 引用组成 。class 引用 指向当前方法在运行时常量池中对应的 class) 。
我们在前面反编译的代码中已经看到过这些内容 。
文章插图
局部变量数组 也称为 局部变量表(LocalVariableTable), 其中包含了方法的参数,以及局部变量 。局部变量数组的大小在编译时就已经确定: 和局部变量+形参的个数有关,还要看每个变量/参数占用多少个字节 。操作数栈是一个 LIFO 结构的栈,用于压入和弹出值 。它的大小也在编译时确定 。
有一些操作码/指令可以将值压入“操作数栈”; 还有一些操作码/指令则是从栈中获取操作数,并进行处理,再将结果压入栈 。操作数栈还用于接收调用其他方法时返回的结果值 。
4.7 方法体中的字节码解读
看过前面的示例,细心的同学可能会猜测,方法体中那些字节码指令前面的数字是什么意思,说是序号吧但又不太像,因为他们之间的间隔不相等 。看看 main 方法体对应的字节码:
0: new #2 // class demo/jvm0104/HelloByteCode3: dup4: invokespecial #3 // Method "<init>":()V7: astore_18: return
间隔不相等的原因是, 有一部分操作码会附带有操作数, 也会占用字节码数组中的空间 。例如,new 就会占用三个槽位: 一个用于存放操作码指令自身,两个用于存放操作数 。
因此,下一条指令 dup 的索引从 3 开始 。
如果将这个方法体变成可视化数组,那么看起来应该是这样的:
文章插图
每个操作码/指令都有对应的十六进制(HEX)表示形式,如果换成十六进制来表示,则方法体可表示为HEX字符串 。例如上面的方法体百世成十六进制如下所示:
文章插图
甚至我们还可以在支持十六进制的编辑器中打开 class 文件,可以在其中找到对应的字符串:
推荐阅读
- MyBatis自动生成工具,开发编码好帮手
- Python 常用的十行代码,建议收藏
- 绕过rar密码提取文件怎么操作?
- Java,OpenCV,图像阈值分割,阈值化,二值阈值化、截断阈值化等
- nest.js + sms 实现短信验证码登录
- 如何在开发中规避硬编码
- 十分钟搞懂手机号码一键登录
- 每天敲代码不到 1 小时,程序员每天都在做什么?
- 无代码系统搭建排班管理应用
- Java 日志记录—记录什么和不记录什么?