(转)JAVA虚拟机执行模型

1.JAVA虚拟机执行模型

在JVM执行模型里,每个方法都是在线程中执行,而每个线程对应自己的栈,每个栈由帧组成。每个帧对应一个方法调用,每次调用一个方法,

会将新帧压入当前线程的执行栈,当方法返回时(异常退出也是返回),再将这个帧从执行栈弹出。

每个帧主要包括两部分,一个局部变量表和一个操作数栈,关系如下图所示:

image

这里注意,局部变量表是根据索引访问的列表,类似数组;而操作数栈则是“后入先出”的栈,这里非常重要,因为java函数的字节码指令基本上都是对这两个数据结构进行操作。

局部变量表和操作数栈的大小取决于方法代码,在编译时计算,并随字节码指令一起写入class文件中,

public int gogo() { 
    Log.i("zkw", "hello");
    return 888;
}

这是一个java方法,编译成class之后内容如下:

 // access flags 0x1
  public gogo()I
    LDC "zkw" 
    LDC "hello" 
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
    SIPUSH 888 
    IRETURN
    MAXSTACK = 2 
    MAXLOCALS = 1

最下面两行的MAXSTACK和MAXLOCALS的值就是操作数栈和局部变量表的大小。

局部变量表和操作数栈中的每个槽(slot)可以保存除long和double之外的任意java值,而long和double需要两个槽,比如向局部变量表储存一个int和一个long,则表中第一个位置是int值,第二和第三个位置存的是long值。

还有一点需要注意,如果是非静态方法,局部变量表的第0个位置为"this"。

2.字节代码指令

Java类型被编译成class后,都是用类型描述符表示的,如下图:

image

方法也同样会被编译成方法描述符,如下:

image

字节码指令是由操作码和参数组成:

  • 操作码是一个字节代码名,由助记符号表示,例如操作码0,对应的是NOP,表示无任何操作的指令;操作码21,对应ILOAD,表示读取局部变量表某个位置的int值。
  • 参数是储存在编译后代码中的静态值。

字节码指令分为两种:

  • 一种是用来在局部变量表和操作数栈之间传送值的。比如FSTORE i指令从操作数栈弹出一个float值,并存入索引i对应的局部变量表中。而DLOAD j指令则是读取局部变量表中索引j和j+1对应的double值(思考一下为什么是j和j+1),并将它压入操作数栈。
  • 另一部分字节码指令仅用来处理操作数栈。比如xADD(x对应I、L、F、D)指令从操作数栈弹出两个数值做加法,然后将结果压入栈。再比如INVOKESTATIC用于调用静态方法,该指令会从操作数栈弹出n+1个值(n是静态方法的n个参数,+1对应目标对象),并压回方法调用的结果。

还是用上面的代码举例子,我们直接看字节码:

 // access flags 0x1
  public gogo()I
    LDC "zkw" 
    LDC "hello" 
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
    SIPUSH 888 
    IRETURN
    MAXSTACK = 2 
    MAXLOCALS = 1

LDC是将参数中的值压入操作数栈,所以前两行执行完,操作数栈应该长这样[...,"zkw","hello"],前面...是之前压入的值,

然后INVOKESTATIC指令弹出之前压入的参数,然后调用Log.i静态方法,最后将int结果压入栈,此时操作数栈应该长这样[...,int结果]

由于没有使用Log.i的返回值,所以直接将返回值从操作数栈POP出去,

接下来SIPUSH将888压入操作数栈,此时栈长这样[...,888]

然后IRETURN从操作数栈弹出int值并返回,方法调用结束。

这里我们没有看到对局部变量表的操作,下面稍微修改下gogo方法:

public int gogo() { 
      int a = Log.i("zkw", "hello"); 
      return a;
}

为了看到如何操作局部变量表,我们获取Log.i返回的int值,并将其return,编译之后如下:

  // access flags 0x1
  public gogo()I
    LDC "zkw" 
    LDC "hello" 
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    ISTORE 1 
    ILOAD 1 
    IRETURN
    MAXSTACK = 2 
    MAXLOCALS = 2

当INVOKESTATIC指令执行之后,操作数栈为[...,int值],局部变量表为[this]

看到INVOKESTATIC之后,多了个ISTORE指令,ISTORE 1指令是弹出操作数栈栈顶的值(也就是log.i的返回值),将其存入局部变量表索引为1的位置(思考一下为什么不是0),当ISTORE执行完,操作数栈为[...],局部变量表为[this,int值]。

然后执行ILOAD 1,该指令取出局部变量表1位置的值,并压入操作数栈,此时操作数栈为[...int值],局部变量表为[this]。

然后IRETURN从操作数栈弹出int值,并将其return,执行结束。

3.栈映射帧

java1.6之后还引入了栈映射帧,用于加快虚拟机中类验证过程的速度。这个映射帧主要记录每个指令执行前的局部变量表和操作数栈中包含的类型状态。这个帧和所谓的栈帧没有关系,这个映射帧仅仅标示当前局部变量表和操作数栈的状态。

当jvm进入一个方法时,根据方法描述符就可以确定初始帧的状态,例如方法com.demo.Foo.gogo(int a)的局部变量表的初始状态为[com.demo.Foo, I],而操作数栈初始状态肯定是空的。所以这个方法的初始帧为[com.demo.Foo, I],[]

为了节省空间,编译方法时并不会为每条指令生成一个映射帧,事实上,它仅为跳转指令(包括if else,try cache等)生成映射帧。

为了节省更多空间,对每个需要生成映射帧的地方做压缩,仅仅储存与前一帧的差别,比如与前一帧的状态一样时,使用F_SAME助记符,当比前一帧增加了3个以内的局部变量时,使用F_APPEND [],当增加了3个以上的局部变量时,使用F_FULL []。说了这么多可能有点晕了,看例子吧。

我们修改上面的例子,增加一些局部变量和条件判断:

   public int gogo(int c) { 
        int a = Log.i("zkw", "hello"); 
        float f = 0.4f;
        if (a > 0) {
            Log.i("zkw", ">>0");
        } else {
            Log.i("zkw", "<<0");
        } 
        return a;
    }

代码中增加了两个局部变量a和f,看看编译后的字节码:

  // access flags 0x1
  public gogo(I)I
    LDC "zkw" 
    LDC "hello" 
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    ISTORE 2 
    LDC 0.4 
    FSTORE 3 
    ILOAD 2 
    IFLE L0
    LDC "zkw" 
    LDC ">>0" 
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
    GOTO L1
    L0
        FRAME APPEND [I F]
    LDC "zkw" 
    LDC "<<0" 
    INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
   L1
       FRAME SAME
   ILOAD 2 
   IRETURN
    MAXSTACK = 2 MAXLOCALS = 4

我们假定这个方法是com.demo.Foo类的,那么这个方法的初始帧状态应该是[com.demo.Foo, I],[],字节码中不会标示初始帧状态。

然后代码继续往下走,我们增加了两个局部变量int a和float f,所以帧状态出现变化,这个变化会在第一个跳转目标里展示出来,请看L0下面的FRAME APPEND [I F],意思是相比于之前的帧状态增加了两个局部变量,类型是int和float,此时帧状态更新成[com.demo.Foo, I, I, F],[]。

之后遇见了下一个跳转目标L1,这时候的局部变量没有变化,所以使用FRAME SAME标示。

这些FRAME指令仅仅是标示帧状态的变化,没有对局部变量表和操作数栈做任何操作,目的是加快java虚拟机中类验证过程的速度。

之前说F_APPEND是标示增加3个之内的帧变化,那3个之外呢,我们继续修改gogo方法,增加两个局部变量:

public int gogo(int c) { 
    int a = Log.i("zkw", "hello"); 
    float f = 0.4f; 
    short s = 12;
    long l = 10003983839L;
    if (a > 0) {
            Log.i("zkw", ">>0");
    } else {
            Log.i("zkw", "<<0");
    } 
    return a;
}

看到我们增加了short s和long l,看看编译后啥样:

 // access flags 0x1
  public gogo(I)I
    LDC "zkw" LDC "hello" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    ISTORE 2 LDC 0.4 FSTORE 3 BIPUSH 12 ISTORE 4 LDC 10003983839 LSTORE 5 ILOAD 2 IFLE L0
    LDC "zkw" LDC ">>0" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
    GOTO L1
   L0
   FRAME FULL [com/demo/Foo I I F I J] []
    LDC "zkw" LDC "<<0" INVOKESTATIC android/util/Log.i (Ljava/lang/String;Ljava/lang/String;)I
    POP
   L1
   FRAME SAME
    ILOAD 2 IRETURN
    MAXSTACK = 2 MAXLOCALS = 7

看到标红的那行,使用了FRAME FULL的指令,后面参数就是完全的局部变量表状态。

转自:http://www.cnblogs.com/coding-way/p/6600647.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容