写文章,标题真是个头疼的事儿。写的偏技术点,可能被认为太生硬。写的吸引点儿,可能被认为是「广告」,看着每次阅读量都不到 3%,不由得「老泪纵横」...
如果本文对你有帮助,转发到朋友圈和「在看」支持一下啊。
扯远了,回到我们的正题。不知道你有没有觉得, JVM 也像我们人或者生物一样,执行的过程一如咱们吃东西。只不过他吃的是 .class 文件,把其中认为有营养的常量池、字节码指令等消化吸收,同时一边把垃圾处理掉,在最后不用的时候,再把全部的垃圾unload。
整个 .class 文件中, 字节码指令是很重要的一个部分,所有方法内的逻辑,都是通过这些指令来完成操作。
今天咱就一起来看看指令。
我们前面说过,指令集(ISA)的实现,一般有两种形式
两者各有优劣,但对于 JVM 来说,设计者在初期就已经明确了场景和目标,所以JVM实现的指令集是基于栈实现的,具有指令数量少,格式简单,操作数少,易于理解和实现等等特点。
一般一个典型的指令集系统中,需要实现的操作分为以下几类:
用通俗的语言描述的话,JVM 这些指令,按革命分工不同,大概干的事儿有:
1.像搬运工一样,来回在局部变量区和操作数栈这两个地方来回挪动数据。比如从局部变量区加载到操作数栈,计算一下,再保存回局部变量区。
2.像手艺人一样,做些打磨加工的工作,把石头做成雕塑类似的类型转换。比如把int 转成long,把double 转成int这些,对应的JVM 指令是i2l和d2i 2前面是源类型,后面是目标类型。
3.新的生命的孕育,像对象的创建、数组的创建等,以及对类型的操作。创建一个新的类实例 new, 新建一个数组 newarray比如getstatic 是访问类的static 域 、getfield 获取类的实例域 判断对象是否属于特定类型的instanceof
4.像红绿灯一样,指导道路的通行方向,来控制程序流程。有条件的转移:像咱们常用的 if (x == 1) 这种,到了字节码的时候,就变成了if_icmpne还有像try-catch字节码里常看到的 goto,做无条件的跳转。还有一些复合条件的转移,像tableswitch 来支持 switch 语法。而对于 switch 能支持 String ,则是通过编译的时候,把 String 对应的 hashCode取出来,做为int 值来使用,通过 lookupswitch 来处理 case 不连续的情形。
5.像你我程序员一样 :-),在 PM 提过来需求之后, 负责把它实现出来,在JVM里这些是运算指令的活儿。比如int 加法iadd, int 减法isub, 递增iinc这些。
6.还有些函数的调用,执行的返回等等,对于静态和非静态方法,对应的指令稍有差别。像 invokevirtual是调用普通实例方法的,invokestatic 是调用类的静态方法的。以及类的初始化方法init,是通过 invokespecial调用的。方法调用完,一般通过 return结束调用,返回 void, 如果是返回类型数据,则是return,这里的T 和咱们前面说的各种代表数据类型的一样,比如返回int类型的值,对应的指令是 ireturn。
7.异常的情况,通过 athrow指令,抛出去。异常的处理原理,可以参考上一篇文章:你写下的try-catch-finally,在JVM看来不过是...
如果对这一部分感兴趣,日常开发中,有几个小工具可以使用。
1.像Java 自带的javap 开箱即用。
2.一个图形界面的工具jclasslib
3.IDEA 里面可以安装工具 jclasslib 对应的插件。
相比 javap,图形界面工具除了使用方便,不用命令行,可以方便查看自己编写的代码生成的字节码到底是哪些外,同时各个方法内对应的字节码指令,只要点击一下就能跳转到指令的官方说明,也方便理解和学习。
比如上面的 iconst_2 指令,会跳转到 Oracle 的这个说明页面