Giter VIP home page Giter VIP logo

Comments (11)

bitfishxyz avatar bitfishxyz commented on August 21, 2024

jvm指令是如何执行的?

接下来就是我们的重点内容了,Jvm指令是如何执行的。

jvm规定了一系列的指令来用来决定jvm的流程,
这些指令打给有下面的类型:

  • 加载和存储指令,比如 iload 将一个整形值从局部变量表加载到操作数栈
  • 控制转移指令,比如条件分支 ifeq
  • 对象操作,比如创建类实例的指令 new
  • 方法调用,比如 invokevirtual 指令用于调用对象的实例方法
  • 运算指令和类型转换,比如加法指令 iadd
  • 线程同步,monitorenter 和 monitorexit 两条指令来支持 synchronized 关键字的语义
  • 异常处理,比如 athrow 显式抛出异常

接下里我会列举出一线典型的指令来和大家一起学习,但是我不可能说道所有的指令,后续的内容需要大家自行查看jvm规范。

我们先开看看这个简单的方法

    public int add(int a, int b){
        return a + b;
    }

它编译后的指令如下:

  public int add(int, int);
    descriptor: (II)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 3: 0

然后我们首先来看一些,stack: 我们的jvm指令调动是基于栈的,这是什么意思呢,就是说我们的

把变量加载到执行栈:进行一些操作,将结果返回或者存储到本地变量。

stack表明我们的栈的最大深度是2,这是编译阶段编译器解析出来的。

locals 是本地变量的数量,他就是 this, a, b。这里注意,对于jvm来说,他不认识什么this,a,b。他只知道本地变量表中的第一个元素,第二个元素,第三个元素。。。。这一点非常重要,我们后续的指令完全是基于所以完成变量的加载的。这一点请务必牢记。

然后我介绍下上面四个指令的含义,jvm的指令非常多,我不可能一一介绍,建议大家在阅读完我的教程后,细读jvm规范。

iload_1 将本地变量表中索引为1的元素加入到操作栈中

  • 针对的int类型的,如果float类型的,fload_1, double类型的就是dload_1

同理iload_2 就是将本地变量表中索引为2的元素加入栈中

iadd就是取出栈顶的连个元素,让它们相加,再将结果放入栈中
pop two values from stack,retuqire them are int, and add them, then push result to stack

ireturn将栈顶的元素返回。
pop value from stack and return this value。

然后我们来通过图示看一看 add(10, 20)?

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

20190705150320

20190705150337

20190705150350

20190705150401

20190705150413

20190705150430

20190705150320

https://www.processon.com/view/link/5d1def06e4b0316252cfa2bf

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

怎么样,是不是很清楚? 然后我们再来看一个示例

public class Hello {
    public int add(int a, int b){
        int c = a + b;
        return c;
    }
}
    Code:
      stack=2, locals=4, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: istore_3
         4: iload_3
         5: ireturn

前面几个步骤都是一样的,我就跳过了,这是初始状态
20190705152124

然后 istore_3
aa

istre后栈空了

20190705152251

iload

20190705152339

ireturn

20190705153305

发现了吗?前者后后者的功能一模一样,但是前者多了一个本地变量c以及两个没有意义的指令,所以写代码的时候尽量选择前者。

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

下面再来看一个有意思的例子。已知字节码指令为

  public int action(int, int, int);
    descriptor: (III)I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: iload_3
         1: iload_1
         2: isub
         3: iload_2
         4: isub
         5: ireturn
      LineNumberTable:
        line 3: 0
}

请问你可以反向推导出对应的java代码吗?

提示
isub 这个指令是:

  • pop a value from stack,call value1, require it is int
  • pop a value from stack, call value2, require it it int
  • push(value2 - value1) to stack

让栈顶的两个int型元素相减

假设三个变量是
首先很明显 ,长这样的

public class Hello {
    public int action(int param1, int param2, int param3){
    }
}
 0: iload_3
1: iload_1
2: isub
c -b

3: iload_2

c -b -a

4: isub
5: ireturn
return 

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

20190705155854

20190705155904

20190705155913

20190705155920

20190705155932

20190705155940

20190705155947

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

那么你明白结果了吗?

return param3 - param1 -param2

怎么样?是不是很有趣?

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

if else

接下里我们分析下if else 是如何运行。
这是我们的java代码,很假单,就是返回两个值中比较大的那个。

public class Hello {
    public static int maxValue(int p1, int p2){
        if (p1 > p2){
            return p1;
        } else {
            return p2;
        }
    }
}

下面是编译后的字节码

  public static int maxValue(int, int);
    descriptor: (II)I
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: iload_0
         1: iload_1
         2: if_icmple     7
         5: iload_0
         6: ireturn
         7: iload_1
         8: ireturn

然后你注意到了吗,这里的本地变量只要两个。应为我们的static方法 没有this。

然后就是if_complet

假设当前的操作是栈是这样的

// here is top of stack
value1
value2
...
// here is buttom of stac

这时候如果我们执行if_icmple 7,如果value2 < value1 那么指令跳转到7的位置,否者不跳转,安装顺序向下执行。

这是图示:

20190705173040

20190705173054

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

for循环

if_icmple 是一个跳转指令,还有一个goto

java中goto是不合法的,但是在指令集中有。我们来看一下

public class Hello {
    public static void loop(){
        for (int i = 0; i < 3; i++) {
        }
    }
}
    Code:
      stack=2, locals=1, args_size=0
         0: iconst_0
         1: istore_0
         2: iload_0
         3: iconst_3
         4: if_icmpge     13
         7: iinc          0, 1
        10: goto          2
        13: return

接受三个新的指令,

iconst_0 将 0压入操作栈
iconst_1 将 1 压入操作栈
iinc 将本地变量表中,索引为0的本地的值+1

goto,跳转到对应的指令位置执行。

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

20190705175035

20190705175047

20190705175056

20190705175105

20190705175113

20190705175121

20190705175130

20190705175140

20190705175148

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

上面的代码中,我们执行了一遍for循环。显然,第三次的时候i的值会到3,将会跳出循环。

from blogss.

bitfishxyz avatar bitfishxyz commented on August 21, 2024

Switch 原理

如果让你来设计一个 switch-case 的底层实现,你会如何来实现?是一个个 if-else 来判断吗? 实际上编译器将使用 tableswitch 和 lookupswitch 两个指令来生成 switch 语句的编译代码。为什么会有两个呢?这充分体现了效率上的考量。

int chooseNear(int i) {
    switch (i) {
        case 100: return 0;
        case 101: return 1;
        case 104: return 4;
        default: return -1;
    }
}

字节码如下:

0: iload_1
1: tableswitch   { // 100 to 104
         100: 36
         101: 38
         102: 42
         103: 42
         104: 40
     default: 42
}

36: iconst_0   // return 0
37: ireturn
38: iconst_1   // return 1
39: ireturn
40: iconst_4   // return 4
41: ireturn
42: iconst_m1  // return -1
43: ireturn

细心的同学会发现,代码中的 case 中并没有出现 102、103,为什么字节码中出现了呢? 编译器会对 case 的值做分析,如果 case 的值比较紧凑,中间有少量断层或者没有断层,会采用 tableswitch 来实现 switch-case,有断层的会生成一些虚假的 case 帮忙补齐连续,这样可以实现 O(1) 时间复杂度的查找:因为 case 已经被补齐为连续的,通过游标就可以一次找到。

伪代码如下

int val = pop();                // pop an int from the stack
if (val < low || val > high) {  // if its less than <low> or greater than <high>,
    pc += default;              // branch to default 
} else {                        // otherwise
    pc += table[val - low];     // branch to entry in table
}

我们来看一个 case 值断层严重的例子

int chooseFar(int i) {
switch (i) {
case 1: return 1;
case 10: return 10;
case 100: return 100;
default: return -1;
}
}

对应字节码

0: iload_1
1: lookupswitch  { // 3
           1: 36
          10: 38
         100: 41
     default: 44
}

如果还是采用上面那种 tableswitch 补齐的方式,就会生成上百个假 case,class 文件也爆炸式增长,这种做法显然不合理。lookupswitch应运而生,它的键值都是经过排序的,在查找上可以采用二分查找的方式,时间复杂度为 O(log n)

结论是:switch-case 语句 在 case 比较稀疏的情况下,编译器会使用 lookupswitch 指令来实现,反之,编译器会使用 tableswitch 来实现

from blogss.

Related Issues (6)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.