JVM分析

本文地址:JVM分析
- 关于JVM执行模型。Java虚拟机(JVM)是基于栈结构的,也就是Java代码运行在Java虚拟机线程中,而每一个线程拥有多个执行栈,执行栈由多个帧组成,每执行一个方法,则在执行栈中产生一个帧,即方法被触发时,一个新产生的帧将被Push到当前线程的执行栈中,当该方法执行完毕正常返回或者发生异常时,该帧将被Pop出执行栈。

  • 关于帧。每个帧由本地变量(Local Variables)和操作栈(Operand Stack)两部分组成。

变量

局部变量

java
int i=5;

被编译成:

java
0: bipush 5
2: istore_0

操作码说明
bipush xpush一个Byte类型的x作为Int整型值到操作栈,上例中将5加入到操作栈
istore_y将操作栈中的相应的Int整型值变量pop出操作栈,然后存储到本地变量数组中,y表示在操作栈中的位置。

内存中转换情况:

成员变量

public class SimpleClass {
    public int simpleField = 100;
}

被编译成;

public SimpleClass();
  Code:
    stack=2, locals=1, args_size=1
       0: aload_0
       1: invokespecial #1      //调用Object的init构造方法
       4: aload_0
       5: bipush        100
       7: putfield      #2      //成员变量simpleField` 类型为int整型
      10: return
操作码说明
aload_0读取帧本地变量的值然后push到操作栈中。即从局部变量表的相应位置装载一个对象引用到操作数栈的栈顶。本例代码中没有构造函数,但是编译器会默认创建相应的构造函数,因此该变量实际上指向this,aload_0把this装载到操作数栈中。这里的0表示在局部变量表中的相应位置,然后将对象的引用装载到操作数栈中。
invokespecial该指令调用实例的初始化方法,包含私有方法以及当前类的父类方法。它属于一组以不同方式调用方法的操作码,该组操作码有:invokedymanic、invokeinterface、invokestatic、invokevirtual。其中invokespecial用于调用父类构造方法的指令,例如java.lang.Object的构造方法。
bipush将int整型值放入操作栈中,这里是将100放入操作栈中。(该指令同上节)
putfield从运行时的常量池中取出一个指向成员变量的引用,然后该成员变量的值及其对应的对象都会从操作数栈中弹出。本例中,成员变量为SimpleField,首先第4步aload_0向操作数栈中添加了对象,然后bipush向 操作数栈中添加了100,最后putfield从栈中弹出这两个值,这个对象的simpleField的值被设为了100。

条件选择

if-else

public int greaterThen(int intOne, int intTwo) {
    if (intOne > intTwo) {
        return 0;
    } else {
        return 1;
    }
}

该方法会编译成以下字节码:

0: iload_1
1: iload_2
2: if_icmple     7
5: iconst_0
6: ireturn
7: iconst_1
8: ireturn

首先使用iload_1iload_2将两个参数加载到操作栈中,然后使用if_Icomple比较操作栈顶的两个值大小,如果intOne小于intTwo,则跳转到位置7,否则继续。

if_icomple:比较栈顶两个int值大小,小于或等于0时跳转

ireturn:从当前方法返回int

注:

iconst_0表示将int类型的0 push进操作栈,该组还有其他类型的操作指令

iconst_opush Int类型0进栈
aconst_nullpush null进栈
fconst_0push float类型进栈
dconst_0push double类型进栈
bipushpush byte类型进栈(单字节常量值-128~127)
sipushpush short类型进栈(端正新常量值-32768~32767)

示例2:

public int greaterThen(float floatOne, float floatTwo) {
    int result;
    if (floatOne > floatTwo) {
        result = 1;
    } else {
        result = 2;
    }
    return result;
}

编译成的字节码:

0: fload_1
 1: fload_2
 2: fcmpl
 3: ifle          11
 6: iconst_1
 7: istore_3
 8: goto          13
11: iconst_2
12: istore_3
13: iload_3
14: ireturn
  • fload_1fload_2将参数推送至栈顶
  • fcmpl:比较栈顶两个float类型值大小,将结果(1、0、-1)推送栈顶。若有一个数值为NaN,将-1推送栈顶
  • ifle:栈顶int值小于0跳转
  • istore_3:将栈顶int值传入第三个本地变量

Switch

switch表达式支持char、byte、short、int、string或enum类型,JVM对于Switch提供了两个指令:tableswitch以及lookupswitch,它们都支持对int值进行操作,而char、byte、short以及enum都可以在内部转换成为int。

  • tableswitch:用于switch条件跳转,case值连续
  • lookupswitch:用于switch条件跳转,case值不连续

tableswitch

tableswitch是列出最大值与最小值之间所有的case值(不管它是否出现在Java代码中),如果JVM发现switch变量不在最值范围内,则直接跳转到default代码块,在最值范围内,如果出现在代码中,则跳转到相应位置,若没有出现在代码块中,跳转到default处。它可以保证最值范围内每个case值都有相应的结果。

示例:

public int simpleSwitch(int intOne) {
    switch (intOne) {
        case 0:
            return 3;
        case 1:
            return 2;
        case 4:
            return 1;
        default:
            return -1;
    }
}

被编译成:

0: iload_1
 1: tableswitch   {
         default: 42
             min: 0
             max: 4
               0: 36
               1: 38
               2: 42
               3: 42
               4: 40
    }
36: iconst_3
37: ireturn
38: iconst_2
39: ireturn
40: iconst_1
41: ireturn
42: iconst_m1
43: ireturn

这里tableswitch有0、1和4三个值,每个值对应了相应的代码块,同时还包含了最值范围内其他的值2和3,不过他们不是需要寻找的case值,所以指向了default处。

当指令执行时,首先判断是否在最值内,如果不在则直接转到default(default索引总是出现在第一个字节)处,如果她在最值中,则依次寻找满足的条件跳转到相应的位置。

lookupswitch

如果最值范围太大,而case值很稀疏,那么tableswitch则会消耗很多内存。此时使用lookupswitch更好,它只会列出对应的case值的跳转,而不是每一个介于最值间的跳转。

示例:

public int simpleSwitch(int intOne) {
    switch (intOne) {
        case 10:
            return 1;
        case 20:
            return 2;
        case 30:
            return 3;
        default:
            return -1;
    }
}

被编译成:

0: iload_1
 1: lookupswitch  {
         default: 42
           count: 3
              10: 36
              20: 38
              30: 40
    }
36: iconst_1
37: ireturn
38: iconst_2
39: ireturn
40: iconst_3
41: ireturn
42: iconst_m1
43: iretur

这里为了保证算法的性能,索引匹配的值是有序的。

循环

While循环

示例:

public void whileLoop() {
    int i = 0;
    while (i < 2) {
        i++;
    }
}
0: iconst_0
 1: istore_1
 2: iload_1
 3: iconst_2
 4: if_icmpge     13
 7: iinc          1, 1
10: goto          2
13: return
  • iconst_0:将int型0放入操作栈
  • istore_1:将操作栈顶0存入本地变量
  • iload_1:将第二个本地变量推送至栈顶
  • iconst_2:将int型2推送至栈顶
  • if_icmpge:比较栈顶两个int型值大小,结果大于0时跳转(此时如果栈底大于栈顶值,则跳出while循环,转到13步,直接return)
  • iinc:将指定int型变量增加指定值,这里iinc 1,1:在局部变量表第一个值加1

for循环

for循环与while循环在字节码层面上是相同的,因为所有while循环都可以转换成为for循环,例如上例中的while循环可以转换成如下的for循环,他们的字节码也是之前那个。

public void forLoop() {
    for(int i = 0; i < 2; i++) {
        //do nothing
    }
}

do-while循环

示例:

public void doWhileLoop() {
    int i = 0;
    do {
        i++;
    } while (i < 2);
}

被编译成:

0: iconst_0
 1: istore_1
 2: iinc          1, 1
 5: iload_1
 6: iconst_2
 7: if_icmplt     2
10: return
  • iconst_0:int型0放入操作栈
  • istore_1:将0放入本地变量
  • iinc:本地变量0加1
  • iload_1:此处将本地变量载入操作栈(由于是i++,所以返回原来的值,还是0)
  • iconst_2:将2载入栈顶
  • if_icmplt:进行比较,如果大于0 ,转到第二步继续循环

文献

从Java代码到字节码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值