2019-07-11

Java字节码详解(二)字节码的运行过程

2018年10月23日 17:31:04 talex 阅读数 677

<article class="baidu_pl" style="box-sizing: inherit; outline: 0px; margin: 0px; padding: 16px 0px 0px; display: block; position: relative; color: rgba(0, 0, 0, 0.75); font-family: -apple-system, "SF UI Text", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; font-size: 14px; font-style: normal; font-variant-ligatures: common-ligatures; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">

文章目录

前一章讲述了java字节码文件的生成以及字节码文件中各个字段代表的含义,在本章节将讲述字节码是什么运行的

JVM的一些基础概念

要理解java字节码的运行情况,首先要了解有关JVM的一些知识,这些是java字节码运行的先决条件。

JVM数据类型

Java是静态类型的,它会影响字节码指令的设计,这样指令就会期望自己对特定类型的值进行操作。例如,就会有好几个add指令用于两个数字相加:iadd、ladd、fadd、dadd。他们期望类型的操作数分别是int、long、float和double。大多数字节码都有这样的特性,它具有不同形式的相同功能,这取决于操作数类型。
JVM定义的数据类型包括:

  1. 基本类型:
    • 数值类型: byte (8位), short (16位), int (32位), long (64-bit位), char (16位无符号Unicode), float(32-bit IEEE 754 单精度浮点型), double (64-bit IEEE 754 双精度浮点型)
    • 布尔类型
    • 指针类型: 指令指针。
  2. 引用类型:
    • 数组
    • 接口

在字节码中布尔类型的支持是受限的。举例来说,没有结构能直接操作布尔值。布尔值被替换转换成 int 是通过编译器来进行的,并且最终还是被转换成 int 结构。Java 开发者应该熟悉所有上面的类型,除了 returnAddress,它没有等价的编程语言类型。类数组接口在字节码中布尔类型的支持是受限的。举例来说,没有结构能直接操作布尔值。布尔值被替换转换成 int 是通过编译器来进行的,并且最终还是被转换成 int 结构。

Java 开发者应该熟悉所有上面的类型,除了 returnAddress,它没有等价的编程语言类型。

JVM的内存结构

Java字节码详解(二)字节码的运行过程

JVM的内存分布如上图所示。方法区和堆是线程共享的,而寄存器、java方法栈、本地方法栈是各个线程私有的。

1.方法区

方法区是用来存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
这个区域很少进行垃圾回收,回收目标主要是针对常量池的回收和对类型的卸载。

2.堆

此区域唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存.

3.PC寄存器

程序计数器是一块较小的内存空间,线程私有。它可以看作是当前线程所执行的字节码的行号指示器

4. Java方法栈和本地方法栈

JVM栈描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息.
Java字节码的运行就是在JVM方法栈中进行的

Java字节码运行过程

简单的示例

1.示例源码

先来看我们的例子代码,源码如下:

public class Test{
    public static void main(String[] args){
        Integer a = 1;
        Integer b = 2;
        Integer c = a + b;
    }
}

2.main函数的字节码展示

使用javac Test.java进行编译,然后使用javap -v Test.class查看该java文件的字节码,为了排除干扰,去除了很多不必要的字节码

  *** 省略部分字节码
 Constant pool:
   #1 = Methodref           #5.#14         // java/lang/Object."<init>":()V
   #2 = Methodref           #15.#16        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Methodref           #15.#17        // java/lang/Integer.intValue:()I                                                                                                                                                                                                                                                                                             
  *** 省略部分字节码

  public static void main(java.lang.String[]);                                                               
   descriptor: ([Ljava/lang/String;)V                                                                        
   flags: ACC_PUBLIC, ACC_STATIC                                                                             
   Code:                                                                                                     
     stack=2, locals=4, args_size=1                                                                          
        0: iconst_1                                                                                          
        1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      
        4: astore_1                                                                                          
        5: iconst_2                                                                                          
        6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      
        9: astore_2                                                                                          
       10: aload_1                                                                                           
       11: invokevirtual #3                  // Method java/lang/Integer.intValue:()I                        
       14: aload_2                                                                                           
       15: invokevirtual #3                  // Method java/lang/Integer.intValue:()I                        
       18: iadd                                                                                              
       19: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;      
       22: astore_3                                                                                          
       23: return                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             

3.字节码指令运行过程

接下来分析Code中字节码运行的过程。这里说一下,每个指令前的数字为指令在寄存器中的偏移量。

0: iconst_1 将int常量1进行放入操作数栈。这里稍微做个拓展,如果将float常量2进行入栈操作,name该指令是fconst_2,详细的指令种类及意义请查看下一章 Java字节码指令详解

在这里插入图片描述

1: invokestatic #2 调用常量池中序号为#2的静态方法,这里调用的是 Integer.valueOf()方法,表示将该int类型进行装箱操作,变为Integer类型

4: astore_1 在索引为1的位置将第一个操作数出栈(一个Integer值)并且将其存进本地变量,相当于变量a。

在这里插入图片描述

5: iconst_2 将int常量2进行放入操作数栈

在这里插入图片描述

6: invokestatic #2 调用常量池中序号为#2的静态方法,这里调用的是 Integer.valueOf()方法,表示将该int类型进行装箱操作,变为Integer类型

9: astore_2 在索引为2的位置将第一个操作数出栈(一个Integer值)并且将其存进本地变量,相当于变量b。

在这里插入图片描述

10: aload_1 从索引1的本地变量中加载一个int值,放入操作数栈

在这里插入图片描述

11: invokevirtual #3 调用常量池中序号为#3的实例方法,这里调用的是 Integer.intValue()方法

14: aload_2 从索引1的本地变量中加载一个int值,放入操作数栈

在这里插入图片描述

15: invokevirtual #3 调用常量池中序号为#3的实例方法,这里调用的是 Integer.intValue()方法

18: iadd 把操作数栈中的前两个int值出栈并相加,将相加的结果放入操作数栈。

在这里插入图片描述

19: invokestatic #2调用常量池中序号为#2的静态方法,这里调用的是 Integer.valueOf()方法

22: astore_3 在索引为3的位置将第一个操作数出栈(一个Integer值)并且将其存进本地变量,相当于变量c。

在这里插入图片描述

23: return 方法结束

方法调用

上面的示例是比较简单的,而且只有一个main函数,接下来将展示在多个函数时候字节码的形式以及运行的具体过程。这里就直接拿参考文章的示例,原文写得真的很好,有条件可以去看英文原文。 字节码的介绍

1.示例源码

public class Test{
    public static void main(String[] args){
        int a = 1;
        int b = 2;
        int c = calc(1,2);
    }

    static int calc(int a,int b){
        return (int) Math.sqrt(Math.pow(a,2)+Math.pow(b,2));
    }
}

2.字节码展示

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: iconst_1
         1: istore_1
         2: iconst_2
         3: istore_2
         4: iconst_1
         5: iconst_2
         6: invokestatic  #2                  // Method calc:(II)I
         9: istore_3
        10: return

  static int calc(int, int);
    descriptor: (II)I
    flags: ACC_STATIC
    Code:
      stack=6, locals=2, args_size=2
         0: iload_0
         1: i2d
         2: ldc2_w        #3                  // double 2.0d
         5: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
         8: iload_1
         9: i2d
        10: ldc2_w        #3                  // double 2.0d
        13: invokestatic  #5                  // Method java/lang/Math.pow:(DD)D
        16: dadd
        17: invokestatic  #6                  // Method java/lang/Math.sqrt:(D)D
        20: d2i
        21: ireturn

3. 指令执行过程详解

上面就是main方法和calc方法的字节码,由于main方法的指令跟上个例子很相似,唯一不同的是 c=a+b变为由calc方法去执行并且返回。这里就不再赘述main方法,接下来主要讲解calc方法的执行过程。

0: iload_0 将方法中第一个参数入栈

在这里插入图片描述

1: i2d将int类型转为double类型

2: ldc2_w #3 将常量池序号为#3的long型常量从常量池推送至栈顶(宽索引)

在这里插入图片描述

5: invokestatic #5 调用静态方法:Math.pow:() ,并且将结果放入栈顶

在这里插入图片描述

8: iload_1
9: i2d
10: ldc2_w #3
13: invokestatic #5
以上的指令跟上一个一样,进行平方运算

在这里插入图片描述

16: dadd 将result和result2相加,并推入栈顶

在这里插入图片描述

17: invokestatic #6 调用Math.sqrt()方法

20: d2i 将double类型转为int类型

21: ireturn 返回int类型的数值

实例调用

修改上面的代码,加入对象,并调用对象的方法。

public class Test {
    public static void main(String[] args){

       Point a  =new Point (1,2);
       Point b = new Point (3,4);
       int c = a.area(b);
    }

    static class Point{
        private int x;
        private int y;

        public Point(int x,int y){
            this.x = x;
            this.y = y;
        }

        public int  area(Point p){
            int length  = Math.abs(p.y-this.y);
            int width  = Math.abs(p.x-this.x);
            return length*width;
        }
    }
}

使用javap -v Test查看编译后的字节码:

  public static void main(java.lang.String[]);                                                    
    descriptor: ([Ljava/lang/String;)V                                                            
    flags: ACC_PUBLIC, ACC_STATIC                                                                 
    Code:                                                                                         
      stack=4, locals=4, args_size=1                                                              
         0: new           #2                  // class Test3$Point                                
         3: dup                                                                                   
         4: iconst_1                                                                              
         5: iconst_2                                                                              
         6: invokespecial #3                  // Method Test3$Point."<init>":(II)V                
         9: astore_1                                                                              
        10: new           #2                  // class Test3$Point                                
        13: dup                                                                                   
        14: iconst_3                                                                              
        15: iconst_4                                                                              
        16: invokespecial #3                  // Method Test3$Point."<init>":(II)V                
        19: astore_2                                                                              
        20: aload_1                                                                               
        21: aload_2                                                                               
        22: invokevirtual #4                  // Method Test3$Point.area:(LTest3$Point;)I         
        25: istore_3                                                                              
        26: return                                                                                

这个main方法比上一个例子多了几个新的指令:new,dup,invokespecial

  • new new 指令与编程语言中的 new 运算符类似,它根据传入的操作数所指定类型来创建对象(这是对 Point 类的符号引用)。

  • dup dup指令会复制顶部操作数的栈值,这意味着现在我们在栈顶部有两个指向Point对象的引用。

    在这里插入图片描述

  • iconst_1iconst_2invokespecial,将x,y的值(1,2)压入栈顶,接下来进行Point初始化工作,将x,y的值进行赋值。初始化完成后会将栈顶的三个操作引用销毁,只留下最初的Point的对象引用。

    在这里插入图片描述

  • astore_1 将该Point引用出栈,并将其赋值到索引1所保存的本地变量(astore_1中的a表明这是一个引用值)

    在这里插入图片描述

接下来进行第二个Point实例的初始化和赋值操作


在这里插入图片描述
  • 20: aload_1,21: aload_2 将a,b的Point实例的引用入栈
  • 22: invokevirtual #4 调用 area方法,
  • 25: istore_3 将返回值放入索引3中(即赋值给c)
  • return 方法结束

总结

本章节写了字节码运行的详细过程,详细的指令介绍在下一章,有兴趣可以看看。

</article>

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

推荐阅读更多精彩内容

  • Java byte code 的学习意义 为啥要学java bytecode,这就跟你问我已经会python了为...
    shanggl阅读 1,655评论 0 3
  • 概述 执行引擎是Java虚拟机最核心的组成部分之一。“虚拟机”是一个相对于“物理机”的概念,这两个机器都有代码执行...
    胡二囧阅读 885评论 2 2
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,575评论 3 83
  • 每个使用Java的开发者都知道Java字节码是在JRE中运行(JRE: Java 运行时环境)。JVM则是JRE中...
    燕京博士阅读 1,400评论 0 6
  • 第二部分 自动内存管理机制 第二章 java内存异常与内存溢出异常 运行数据区域 程序计数器:当前线程所执行的字节...
    小明oh阅读 1,147评论 0 2