深入浅出JVM(十)之字节码指令(下篇)

简介: 深入浅出JVM(十)之字节码指令(下篇)

上篇文章深入浅出JVM(九)之字节码指令(上篇)已经深入浅出说明加载存储、算术、类型转换的字节码指令,本篇文章作为字节码的指令的下篇,深入浅出的解析各种类型字节码指令,如:方法调用与返回、控制转义、异常处理、同步等

使用idea中的插件jclasslib查看编译后的字节码指令

方法调用与返回指令

方法调用指令

非虚方法: 静态方法,私有方法,父类中的方法,被final修饰的方法,实例构造器

与之对应不是非虚方法的就是虚方法了

  • 普通调用指令
  • invokestatic: 调用静态方法
  • invokespecial: 调用私有方法,父类中的方法,实例构造器方法,final方法
  • invokeinterface: 调用接口方法
  • invokevirtual: 调用虚方法
  • 使用invokestaticinvokespecial指令的一定是非虚方法
    使用invokeinterface指令一定是虚方法(因为接口方法需要具体的实现类去实现)
    使用invokevirtual指令可能是虚方法
  • 动态调用指令
  • invokedynamic: 动态解析出需要调用的方法再执行
  • jdk 7 出现invokedynamic,支持动态语言

测试虚方法代码

  • 父类
 public class Father {
     public static void staticMethod(){
         System.out.println("father static method");
     }
 ​
     public final void finalMethod(){
         System.out.println("father final method");
     }
 ​
     public Father() {
         System.out.println("father init method");
     }
 ​
     public void overrideMethod(){
         System.out.println("father override method");
     }
 }
  • 接口
 public interface TestInterfaceMethod {
     void testInterfaceMethod();
 }
  • 子类
 public class Son extends Father{
 ​
     public Son() {
         //invokespecial 调用父类init 非虚方法
         super();
         //invokestatic 调用父类静态方法 非虚方法
         staticMethod();
         //invokespecial 调用子类私有方法 特殊的非虚方法
         privateMethod();
         //invokevirtual 调用子类的重写方法 虚方法
         overrideMethod();
         //invokespecial 调用父类方法 非虚方法
         super.overrideMethod();
         //invokespecial 调用父类final方法 非虚方法
         super.finalMethod();
         //invokedynamic 动态生成接口的实现类 动态调用
         TestInterfaceMethod test = ()->{
             System.out.println("testInterfaceMethod");
         };
         //invokeinterface 调用接口方法 虚方法
         test.testInterfaceMethod();
     }
 ​
     @Override
     public void overrideMethod(){
         System.out.println("son override method");
     }
 ​
     private void privateMethod(){
         System.out.println("son private method");
     }
 ​
     public static void main(String[] args) {
         new Son();
     }
 }

image.png

方法返回指令

方法返回指令: 方法结束前,将栈顶元素(最后一个元素)出栈 ,返回给调用者

根据方法的返回类型划分多种指令

image.png

操作数栈管理指令

通用型指令,不区分类型

  • 出栈
  • pop/pop2出栈1个/2个栈顶元素
  • 入栈
  • dup/dup2 复制栈顶1个/2个slot并重新入栈
  • dup_x1 复制栈顶1个slot并插入到栈顶开始的第2个slot下
  • dup_x2复制栈顶1个slot并插入到栈顶开始的第3个slot下
  • dup2_x1复制栈顶2个slot并插入到栈顶开始的第3个slot下
  • dup2_x2复制栈顶2个slot并插入到栈顶开始的第4个slot下
  • 插入到具体的slot计算: dup的系数 + _x的系数

控制转义指令

条件跳转指令

通常先进行比较指令,再进行条件跳转指令

比较指令比较结果-1,0,1再进行判断是否要跳转

条件跳转指令: 出栈栈顶元素,判断它是否满足条件,若满足条件则跳转到指定位置

image.png

image.png

注意: 这种跳转指令一般都"取反",比如代码中第一个条件语句是d>100,它第一个条件跳转指令就是ifle小于等于0,满足则跳转,不满足则按照顺序往下走

比较条件跳转指令

比较条件跳转指令 类似 比较指令和条件跳转指令 的结合体

image.png

image.png

多条件分支跳转指令

多条件分支跳转指令是为了switch-case提出的

tableswitch用于case值连续的switch多条件分支跳转指令,效率好

lookupswitch用于case值不连续的switch多条件分支跳转指令(虽然case值不连续,但最后会对case值进行排序)

tableswitch

image.png

lookupswitch

image.png

对于String类型是先找到对应的哈希值再equals比较确定走哪个case的

无条件跳转指令

无条件跳转指令就是跳转到某个字节码指令处

goto经常使用

jsr,jsr_w,ret不怎么使用了

image.png

异常处理指令

throw抛出异常对应athrow: 清除该操作数栈上所有内容,将异常实例压入调用者操作数栈上

使用try-catch/try-final/throws时会产生异常表

异常表保存了异常处理信息 (起始、结束位置、字节码指令偏移地址、异常类在常量池中的索引等信息)

athrow

image.png

异常表

image.png

异常还会被压入栈或者保存到异常表中

同步控制指令

synchronized作用于方法时,方法的访问标识会有ACC_SYNCHRONIZED表示该方法需要加锁

synchronized作用于某个对象时,对应着**monitorentry加锁字节码指令和 monitorexit解锁字节码指令**

Java中的synchronized默认是可重入锁

  • 当线程要访问需要加锁的对象时 (执行monitorentry)
  1. 先查看对象头中加锁次数,如果为0说明未加锁,获取后,加锁次数自增
  2. 如果不为0,再查看获取锁的线程是不是自己,如果是自己就可以访问,加锁次数自增
  3. 如果不为0且获取锁线程不是自己,就阻塞

当线程释放锁时 (执行monitorexit)会让加锁次数自减

image.png

为什么会有2个monitorexit ?

程序正常执行应该是一个monitorentry对应一个monitorexit的

如果程序在加锁的代码中抛出了异常,没有释放锁,那不就会造成其他阻塞的线程永远也拿不到锁了吗

所以在程序抛出异常时(跳转PC偏移量为15的指令)继续往下执行,抛出异常前要释放锁

总结

本篇文章作为字节码指令的下篇,深入浅出的解析方法调用与返回,操作数栈的入栈、出栈,控制转义,异常和同步相关字节码指令

方法调用指令分为静态、私有、接口、虚、动态方法等,返回指令则主要是以i、l、f、d、a开头的return指令分别处理不同类型的返回值

操作数栈中的出栈指令常用pop相关指令,入栈(复制栈顶元素并插入)常用dup相关指令

控制转义指令中条件跳转指令是判断栈顶元素来进行跳转,比较条件跳转指令是通过两个栈顶元素比较来判断跳转,多条件分支跳转是满足switch,常在异常时进行goto无条件跳转

异常处理指令用于抛出异常,清除操作数栈并将异常压入调用者操作数栈顶

同步控制指令常使用monitorentrymonitoryexit,为了防止异常时死锁,抛异常前执行monitoryexit

最后

  • 参考资料
  • 《深入理解Java虚拟机》

本篇文章将被收入JVM专栏,觉得不错感兴趣的同学可以收藏专栏哟~

觉得菜菜写的不错,可以点赞、关注支持哟~

有什么问题可以在评论区交流喔~


相关文章
|
6月前
|
安全 Java
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
39 0
|
1月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
33 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
SQL 缓存 Java
JVM知识体系学习三:class文件初始化过程、硬件层数据一致性(硬件层)、缓存行、指令乱序执行问题、如何保证不乱序(volatile等)
这篇文章详细介绍了JVM中类文件的初始化过程、硬件层面的数据一致性问题、缓存行和伪共享、指令乱序执行问题,以及如何通过`volatile`关键字和`synchronized`关键字来保证数据的有序性和可见性。
30 3
|
3月前
|
Java
Java常见JVM虚拟机指令(47个)
Java常见JVM虚拟机指令(47个)
69 3
Java常见JVM虚拟机指令(47个)
|
3月前
|
缓存 前端开发 Java
浅析JVM invokedynamic指令与Java Lambda语法
【8月更文挑战第27天】在Java的演进历程中,invokedynamic指令的引入和Lambda表达式的出现无疑是两大重要里程碑。它们不仅深刻改变了Java的开发模式和性能表现,还极大地推动了Java在函数式编程和动态语言支持方面的进步。本文将从技术角度浅析JVM中的invokedynamic指令及其与Java Lambda语法的紧密联系。
58 0
|
4月前
|
监控 Java Linux
Linux下JVM相关指令详解及案例介绍
Linux下JVM相关指令详解及案例介绍
57 1
|
5月前
|
存储 Java 编译器
JVM系列7-虚拟机字节码执行引擎
JVM系列7-虚拟机字节码执行引擎
32 1
|
4月前
|
存储 运维 Java
Java中的字节码与JVM指令集详解
Java中的字节码与JVM指令集详解
|
6月前
|
Java 索引
【JVM】字节码文件的组成部分
【JVM】字节码文件的组成部分
56 1
|
6月前
|
存储 Arthas Java
【JVM系列笔记】字节码
本文介绍了Java虚拟机(JVM)的组成,包括类加载子系统、运行时数据区、执行引擎和本地接口。字节码文件由基础信息(如魔数和版本号)、常量池、字段、方法和属性组成。常量池用于存储字符串等共享信息,方法区则包含字节码指令。执行引擎包含解释器、即时编译器和垃圾回收器,负责字节码的解释和优化。文章还提到了字节码工具,如javap、jclasslib和Arthas,用于查看和分析字节码。
86 0
【JVM系列笔记】字节码
下一篇
无影云桌面