zoukankan      html  css  js  c++  java
  • [四] java虚拟机JVM编译器编译代码简介 字节码指令实例 代码到底编译成了什么形式

     

    前言简介

     
    前文已经对虚拟机进行过了简单的介绍,并且也对class文件结构,以及字节码指令进行了详尽的说明
    想要了解JVM的运行机制,以及如何优化你的代码,你还需要了解一下,java编译器到底是如何编译你的代码的
    本文不是从最底层的编译原理讲解
    本文是针对java代码,去查看归纳总结编译器的结果行为,从而直观的感受到字节码指令集
    也就是说本文的内容,主要针对的是使用javap 查看字节码文件中方法的code属性中的字节码内容
    让你从java代码  class文件格式,以及字节码指令集 进行一个直观的演示
     
    提醒:
    如果你对字节码指令不了解,而且,没有看过前面的文章,本文可能会轻度不适.
    本文示例只是为了展示
    您应该经常查看你自己的代码的class文件去发现其中的规律
     
    一条普通的指令格式
    <index> <opcode> [ <operand1> [ <operand2>... ]] [<comment>]
    index 表示偏移量 行号  等
    opcode 表示操作码
    operandX表示操作数
    comment 为注释
    比如下图所示
    行号 0 , 操作码 getstatic ,操作数 #24  注释为 Field java/lang/System..................
    image_5b879224_252b_thumb[1]
     
    其中 index  行号/偏移量  可以作为控制跳转指令的跳转目标  比如 goto  8 表示跳转到索引为8的指令上
     
    还有一点需要注意的是,javap查看到的内容,你可以认为是class文件表述的信息,但是绝不能理解为就是class文件中的内容
    比如,class文件中没有操作码的助记符,比如,getstatic ,都是指令的二进制值
    再比如刚才说到的,跳转到指定行号,对于控制转移指令,实际的操作数是在当前指令的操作码集合中的地址偏移量
    并不是那个8
    只不过javap工具按照更适合我们阅读的方式进行了翻译
     

    加载存储与算数指令

    复制代码
    public static void main(String[] args) {
    
    int i = -1;
    int j = 3;
    int k = 5;
    int l = 127;
    int m = 32767;
    int n = 32768;
    
    
    int add = i+j;
    int sub = i-j;
    int mul = j*k;
    int div = j/k;
    int rem = k%j;
    int neg = ~j;
    int inc = i++;
    
    }
    复制代码
    image_5b879224_2d18_thumb[1]
     
     
    -1 ~ 5 使用const加载到操作数栈 其中-1 使用iconst_m1
    -128~127 使用bipush -32768~32767使用sipush
    其余常量池ldc
    store从操作数栈保存到局部变量表
    load加载局部变量到操作数栈
    0. 常量-1 加载到操作数栈
    1. 操作数栈保存到1号局部变量表 也就是 i = -1;
    2. 常量 3 加载到操作数栈       
    3. 操作数栈保存到2号局部变量表 也就是j = 3;
    4. 常量 5 加载到操作数栈   
    5. 操作数栈保存到3号局部变量表 也就是k =5;
    6. 常量 127 加载到操作数栈 
    8. 操作数栈保存到4号局部变量表 也就是l = 127;
    10.常量 32767 加载到操作数栈
    13.操作数栈保存到5号局部变量表 也就是m = 32767;
    15.加载#17号常量池数据到操作数栈
    17. 操作数栈保存到6号局部变量表 也就是n = 32768;
    image_5b879224_4b5a_thumb[1]

    19. 加载1号局部变量到操作数栈 对应 i
    20. 加载2号局部变量到操作数栈  对应 j
    21. 执行iadd指令计算并将结果压入栈顶   对应 i+j;
    22. 保存栈顶元素到7号局部变量
    24. 加载1号局部变量到操作数栈 对应 i
    25. 加载2号局部变量到操作数栈  对应 j
    26.执行isub指令计算并将结果压入栈顶   对应i-j;
    27. 保存栈顶元素减法结果到8号局部变量
    29,30 加载 2号和3号局部变量到操作数栈 也就是j   k
    31  执行imul指令并将结果压栈 j*k
    32 保存栈顶元素乘法结果到9号局部变量
    34.35 加载 2号和3号局部变量到操作数栈 也就是j   k
    36 执行idiv 结果压入栈顶
    37保存idiv结果到10号局部变量
    39.40 加载3号 和 2号 也就是k   j
    41 执行求余irem 结果压入栈顶
    42 栈顶元素结果保存到11号局部变量
    44加载2号局部变量  对应 j 到操作数栈
    45 加载常量-1到操作数栈
    46 执行异或运算结果压入栈顶  (~x = -1 ^ x;)
    47栈顶结果保存到12号局部变量
    49 加载1号局部变量 对应 i
    50 执行增量 1 计算 结果压入栈顶
    53 栈顶结果保存到13号变量
    55 void方法 return返回
     

    类型转换指令

    复制代码
    public static void main(String[] args) {
    boolean bNum = true;
    
    char cNum = 2;
    byte byteNum = 127;
    short sNum = 32767;
    int iNum = 100;
    long lNum = 65536;
    float fNum = 2.5f;
    double dNum = 6.8;
    
    char c1 = (char)byteNum; char c2 = (char)sNum; char c3 = (char)iNum; char c4 = (char)lNum; char c5 = (char)fNum; char c6 = (char)dNum; byte b1 = (byte)cNum; byte b2 = (byte)sNum; byte b3 = (byte)iNum; byte b4 = (byte)lNum; byte b5 = (byte)fNum; byte b6 = (byte)dNum; short s1 = (short)cNum; short s2 = (short)byteNum; short s3 = (short)iNum; short s4 = (short)lNum; short s5 = (short)fNum; short s6 = (short)dNum; int i1 = (int)cNum; int i2 = (int)byteNum; int i3 = (int)sNum; int i4 = (int)lNum; int i5 = (int)fNum; int i6 = (int)dNum; long l1 = (long)byteNum; long l2 = (long)cNum; long l3 = (long)sNum; long l4 = (long)iNum; long l5 = (long)fNum; long l6 = (long)dNum; float f1 = (float)byteNum; float f2 = (float)cNum; float f3 = (float)sNum; float f4 = (float)iNum; float f5 = (float)lNum; float f6 = (float)dNum; double d1 = (double)byteNum; double d2 = (double)cNum; double d3 = (double)sNum; double d4 = (double)iNum; double d5 = (double)lNum; double d6 = (double)fNum; }
    复制代码
     
    javap解析后的内容太长,接下来分段解析

    数据的加载与存储

     
    image_5b879224_1328_thumb[1]
     
    从数据的存储可以看得出来 boolean内部使用的是数值1  也就是1 表示true
     

    数据类型转换为char类型

     
    char byte short int  内部形式均为int  所以转换为char是,使用的全都是 i2c
    long  float double 先转换为int(l2i f2i d2i) 然后在统一使用 i2c 转换为char
    image_5b879225_45f1_thumb[1]
     
     

    数据类型转换为byte 类型

     
    char byte short int  内部形式均为int  所以转换为byte时,使用的全都是 i2b
    long  float double 先转换为int(l2i f2i d2i) 然后在统一使用 i2b 转换为  byte
    image_5b879225_7927_thumb[1]

    数据类型转换为short 类型

     
    还是同样的道理,char byte short int  内部形式均为int  所以转换为short 使用的是   i2s
    long  float double 先转换为int(l2i f2i d2i) 然后在统一使用 i2s 转换为  short
    image_5b879225_7e40_thumb[1]

    数据类型转换为int 类型

    char byte short内部都是int类型.将他们转换为int时,不需要进行转换
    如下图所示,一个load 对应一个store
    long  float double    (l2i f2i d2i)   转换为int
    image_5b879225_4c4a_thumb[1]
     

    数据类型转换为long 类型

    char byte short  int   内部都是int类型.将他们转换为long 时,使用  i2l
    float double   转换为long   f2l d2l
    image_5b879225_209d_thumb[1]
     
     

    数据类型转换为float 类型

    char byte short  int   内部都是int类型.将他们转换为float 时,使用  i2f
    long double   转换为float     l2f  d2f
    image_5b879225_2e9e_thumb[1]
     

    数据类型转换为double 类型

     
    char byte short  int   内部都是int类型.将他们转换为double 时,使用  i2d
      
    long   
    float   转换为double     l2d  f2d
     
    image_5b879225_4660_thumb[1] 
       
     
     

    类相关指令

     
    复制代码
    class Super{
    }
    
    class Sub extends Super{
    }
    
    new Object();
    new Super();
    Super s = new Super();
    new Double(1.5);
    new Sub();
    Sub sub = new Sub();
    复制代码
    image_5b879225_51b9_thumb[1]
     
    new Object();
    new Super();
    没有赋值给局部变量 仅仅是创建对象  调用new之后,堆中对象的引用保存在栈顶
    然后调用构造方法invokespecial
     
    Super s = new Super();
    同上面的,需要调用new 
    因为还需要保存到局部变量
    所以new之后 先copy一个,也就是dup
    然后调用构造方法 invokespecial
    然后从操作数栈保存到局部变量 store
     
    复制代码
    Super super1 = new Super();
    Sub sub = new Sub();
    
    //父类引用可以指向子类
    //子类引用不能指向父类
    //但是对于指向子类的父类引用 可以通过类型转换为子类
    Super subToSuper = sub;
    Sub superToSub = (Sub) subToSuper;
    复制代码
    image_5b879225_2fff_thumb[1]
     
    0 创建Spper
    3 复制
    4 调用构造方法
    7 保存到1号局部变量
    8 创建Sub
    11 复制
    12调用构造方法
    15 保存到2号局部变量
    16 2号加载到操作数栈
    17保存到3号局部变量
    18加载3号局部变量到栈
    19 checkcast 进行校验确认是否可以转换为指定类型 否则报错抛 classCastException
    22 再次保存到局部变量
     
     

    控制转移指令

    复制代码
    void intWhile() {
    int i = 0;
    while (i < 100) {
    i++;
    }
    }
    
    void intDoWhile() {
    int i = 0;
    do {
    i++;
    }
    while (i < 100);
    }
    
    void intFor() {
    int j = 0;
    for(int i =0;i<100;i++) {
    j++;
    }
    }
    复制代码

     

    image_5b879225_6bae_thumb[1]
     
    intWhile()方法
    0. 加载常量0 到操作数栈
    1.保存操作数栈元素到1号局部变量 i= 0;
    2.直接跳转到第8行
    8.1号局部变量加载到操作数栈 也就是i 作为第一个元素
    9.加载常量100到操作数栈 也就是100作为第二个元素
    11.比较大小,如果前者小于后者 也就是如果 i <100 满足 跳转到第5行  否则顺序执行到14 return
    5.给1号局部变量以增量1 增加
    然后 8-->9-->11-->5-->8-->9-->11......往复循环 直到条件不满足,从11 跳转到14 结束
     
    intDoWhile()
    0.加载常量0到操作数栈
    1.保存常量0 到1号局部变量
    2.给1号局部变量以增量1 进行自增
    5.1号局部变量加载到操作数栈
    6.常量100加载到操作数栈
    8,比较大小 如果前者小于后者也就是 1号局部变量 i<100 跳转到第2行
    然后进行往复循环,直到条件不满足,然后顺序到return
     
    intFor()
    0.  加载常量0 到操作数栈
    1.  保存栈顶元素到1号局部变量 j=0;
    2.  加载常量0到操作数栈
    3.  保存栈顶元素到2号局部变量i=0;
    4.  跳转到13行
    13.  加载2号局部变量到操作数栈
    14.  加载常量100到操作数栈
    16.  比较大小,如果前者 2号局部变量 i <100 跳转到7
    7.  1号局部变量以增量1  自增 j++
    10.   2号局部变量以增量1 自增 i++
    13.  2号局部变量加载到操作数栈
    14.  加载常量100到操作数栈
    16.  比较大小,如果前者 2号局部变量 i <100 跳转到7
    往复循环 如果条件不满足 从16 顺序到19 结束方法 return
     
     
    复制代码
    public void fun() {
    int i = 0;
    if(i<2) {
    i++;
    }else {
    i--;
    }
    }
    复制代码
    image_5b879225_6ec9_thumb[1]
     
    0, 加载常量0 到栈顶
    1,保存栈顶元素 (0) 到1号局部变量
    2. 加载1号局部变量到栈顶
    3. 加载常量2 到栈顶
    4,比较
    如果大于后者等于跳转到13 然后1号局部变量 自增1 然后下一步顺序到16 return
    否则就是顺序执行到7 1号局部变量 增量为-1  自增运算 然后到10 ,10为跳转到16 return
     
     
     

    方法调用相关指令

    复制代码
    public void invoker() {
    method(2);
    }
    
    public void method(int i) {
    if(i>5) {
    System.out.println(i);
    }
    }
    复制代码
    image_5b879225_55f1_thumb[1]
     
    invoker()
    0,加载0号 局变量到栈   (上面基本都是第一个数据被保存到1号局部变量,0 号其实是被this 占用了)
    1,加载常量2 到操作数栈
    2.调用实例方法(I)V
    5 return

    method(int)
    0. 加载1号局部变量到操作数栈
    1. 加载常量5 到操作数栈
    2比较如果小于等于 跳转到12行 直接返回
    如果大于
    那么顺序执行到5行       out 是类型为PrintStream的   System中的静态变量
    8 加载1号局部变量到操作数栈
    9 调用实例方法 println  是  PrintStream的实例方法 使用invokevirtual
     

    switch 相关

    复制代码
    int i = 5;
    int j = 6;
    switch (i) {
    case 1:
    j = j + 1;
    break;
    case 3:
    j = j + 2;
    break;
    case 5:
    j = j + 3;
    break;
    default:
    j = j + 4;
    }
    复制代码
    image_5b879225_5641_thumb[1]
     
    0,1,2,4 分别将 5 和 6 加载并存储到1号和2号局部变量
    5.加载1号局部变量到栈  对应 switch (i) {
    然后根据tableswitch 表 进行跳转
    虽然我们只有1,3,5  但是设置了1到5 ,对于2 和 4 直接跳转到default

    40: 2号局部变量 +1
    顺序到43
    43: 跳转到61 return

    46: 2号局部变量 +2
    顺序到49
    49: 跳转到61 return
     
    52: 2号局部变量 +3
    顺序到55
    55: 跳转到61 return

    58 2号局部变量 +4
    顺序到61 return

     
    复制代码
    int j = 6;
    String string = "hehe";
    switch (string) {
    case "A":
    j = j + 1;
    break;
    case "hehe":
    j = j + 2;
    break;
    case "C":
    j = j + 3;
    break;
    default:
    j = j + 4;
    }
    复制代码
    image_5b879226_5e68_thumb[4]
     
    0  加载常量6到栈
    1 保存到 1 号局部变量
    3.加载常量池 #36 到栈
    image_5b879226_6b5c_thumb[1]
    5 保存到2 号局部变量
    6 加载2号局部变量 到栈
    7 复制栈顶元素
    8 复制的元素保存到3号局部变量
    9 调用String实例化方法hashCode
    12, lookupswitch表中,不在类似tableswitch 了,那个是连续的
    lookupswitch  是不连续的
    我们总共有三个case一个default 
    lookupswitch 总共有4项
    "A" 的hashCode  为 65
    "C" 的hashCode为 67
    "hehe" 的hashCode为 3198650  不信的话,自己写个main方法打印下

    经过12行 路由之后跳转到指定的序列
    你会发现三个case他们的过程是一样的
    加载3号局部变量 ,然后将常量 A C  hehe 也加载到栈
    然后调用equal方法进行比较
    image_5b879226_523c_thumb[1]

     
    代码千千万,本文只是找一些基本的示例展示字节码与代码的对应关系,想要熟悉这块
    唯有没事多javap看看你代码的class文件,才能通宵领悟,进而更好地优化你的代码
     
    比如看看下面的一个很典型的例子
    int i = 5;
    int j = 8;
    int k = i+j;
    
    int l = 3+6;
    image_5b879226_3a97_thumb[1]
     
    前一部分:
    0. 常量5 加载到栈
    1,保存到 1号局部变量
    2. 常量8 加载到栈
    4 保存到2号 局部变量
    5,加载1号局部变量
    6, 加载2号局部变量
    7 执行iadd 结果会压入栈顶
    8 栈顶元素保存到3号局部变量
    至此 完成了前三行代码

    后一部分:
    9.常量9 加载到栈   (3+6  已经被计算好了)
    11,保存到4号局部变量
  • 相关阅读:
    要回家了,想到以后..
    面试应注意的问题by JohnPhilips(转自matrix论坛)
    在痛苦中坚持,翻译啊
    数据抓取和分析~~
    不考研,就很闲吗?
    在Swing中使用高级的MVC和POJOs
    测试驱动
    开始找工作了,先记个流水帐
    又是开学的时候了
    开发者想要什么算是我的翻译作品的处女作吧,呵呵,致力于英语学习..
  • 原文地址:https://www.cnblogs.com/JonaLin/p/11089792.html
Copyright © 2011-2022 走看看