在上文中, 我们讲解了 Class 文件中的文件标识, 常量池等内容. 在本文中, 我们就详细说一下剩下的指令集内容, 阐述其分别代表了什么含义, 以及 JVM 团队这样设计的意义.
简介
JVM 指令设计为仅有一个字节长度, 由操作码和紧随其后的零至多个操作数来构成.
这里说到 JVM 的指令仅有一个字节, 这意味着 JVM 在操作超过一个字节长度的数据时, 需要在运行时重建出多字节数据类型的具体数据结构, 例如 Long 等. 这会导致这个操作不是原子操作, 在高并发的情况下, 就有可能会导致错误.
由于 JVM 的操作码长度只有一个字节, 因此设计指令的时候, 需要考虑所有指令加起来不能超过一个字节长度, 正因如此, 有许多数据类型是没有其对应的操作码的, 其操作的方式是将其数据类型进行向上转型为其他的数据类型来参与运算.
例如大多数对于 boolean,byte,short 和 char 类型数据的操作, 实际上是将其转换成 int 类型来处理的.
指令详解
JVM 指令如果详细来说的话有一百多个, 在这里全部展开来描述的, 不免有流水账的嫌疑, 且价值不大, 因此在本文中仅粗略描述一下, 并找了一些关键的指令对其进行详细拆解, 如果读者对其他指令有兴趣的话可以自行 Google 或翻书学习.
全部指令的内容 https://segmentfault.com/a/1190000008722128
加载和存储指令: 用于将数据在栈帧中的局部变量表和操作数栈之间转移(栈帧的布局放在以后的文章 JVM - 内存布局中进行介绍, 在这里读者只要明白其是根据栈进行操作就可以了).eg:load,store;
运算指令: 对两个操作数栈上的值进行计算并重新存入到操作栈顶. eg:add,sub,mul,div,rem,neg,shr,or,and,inc......;
类型转换指令: 将一个值数据类型进行转换为其他的类型. eg:x2x;
对象创建与访问指令: new,newarray(数组和类实例创建和操作是不同的);
操作数栈操作指令: 直接操作操作数栈. eg:pop,swap;
控制转移指令: 有条件或无条件的控制 JVM 从指定的位置执行程序.(可以简单理解为修改程序计数器中的值).eg:if,goto......;
方法调用和返回指令: 根据对象的实际类型进行虚方法分配, 调用类方法, 调用接口方法等. 另外还有根据不同的返回类型的不同返回指令;
异常处理指令: 目前异常处理在 JVM 内部是通过异常表来完成的;
- Exception table:
- from to target type
- 0 8 14 Class java/lang/RuntimeException
- 0 8 29 any
- 14 23 29 any
from 行 到 to 行之间的字节码指令如果出现了 type 以及其子类的类型错误, 就跳转到 target 行对应的字节码指令进行执行;
同步指令: 同步指令是通过管程 (Monitor) 来实现的.
同步方法内部分为方法级的同步和方法内部一段指令的同步.
方法内部的指令, 其实现逻辑是设置方法的访问标志: ACC_SYNCHRONIZED, 如果其被设置了, 表明该同步方法已经被别人调用, 其他对象无法获得管程, 就需要等待, 在获得管程后才能继续执行.
指令内部的同步, 其实现逻辑是通过字节码指令来控制的, 字节码执行到需要同步的指令时, 其会调用 monitorenter 指令进行同步, 此时其他线程无法进入这段指令序列, 当程序正常或异常退出后, 调用 monitorexit 指令进行锁释放, 此时其他线程就可以执行同步方法了.
PS: 上面这段是 synchronized 关键字的本质含义, 其具体的细节放到高并发编程系列文章中详细来说.
总结
通过 Class 文件这个中间文件, JVM 达成了语言无关性和平台无关性两个大突破, 使得 Java 语言不仅达到了 "一次编写, 处处运行", 也使得其他语言只要符合 JVM 规范, 就可以像 Java 一样, 达到超然物外的无关性.
文章在公众号 "iceWang" 第一手更新, 有兴趣的朋友可以关注公众号, 第一时间看到笔者分享的各项知识点, 谢谢! 笔芯!
本系列文章主要借鉴自《深入分析 Javaweb 技术内幕》和《深入理解 Java 虚拟机 - JVM 高级特性与最佳实践》.
来源: https://www.cnblogs.com/JRookie/p/11006941.html