美文网首页
走进JVM-字节码

走进JVM-字节码

作者: 奋斗_登 | 来源:发表于2023-11-05 23:23 被阅读0次

      Java虚拟机,即Java Virtual Machine, 简称JVM。JVM提供商包括Sum、BEA、IBM等。1999年,Sun公司发布了由C/C++实现的HotSpot Java 虚拟机。2006年,在JavaOne大会上开源了其先关核心技术,启动OpenJDK项目,逐步形成了活跃的OpenJDK社区。2010年,Sun公司被Oracle公司收购。Oracle的HotSpot JVM实现,是目前OpenJDK使用的主流JVM ,它采用解释和编译混合执行的模式,其JIT技术采用分层编译,极大的提高了Java的执行速度。BEA的JRockit在2008年被并入HotSpot;IBM的J9也在2017年开源,形成了现在的OpenJ9社区。
       随着互联网的蓬勃发展及AI时代的到来,Java在这些计算领域占据着越来越重要的地位。目前主流的高科技公司都有独立的JVM团队基于OpenJDK开发自己定制版本。如阿里(AlibabaJDK 简称AJDK)、谷歌、亚马逊(Corretto)等等。

    字节码

       0和1是计算机仅能识别的信号,经过0和1不同的组合产生了数字之上的操作。另外,通过不同的组合亦产生了各种字符。同样可以不同的组合产生不同的机器指令。在不同的时代,不同的厂商,机器指令组成的结合是不同的。但毕竟CPU是底层基础硬件,指令集通常以扩展兼容的方式向前不断演进。而机器码是离CPU指令最近的编码,是CPU可以直接解读的指令,因此机器码肯定是与底层硬件系统耦合的。
      如果某个程序因为不同的硬件平台需要编写多套代码,这是十分让人崩溃的。Java的使命就是一次编译,到处执行。在不同的操作系统,不同的硬件平台上,均可以不用修改代码即可顺畅的执行,如何实现跨平台? 计算机工程领域的任何问题都可以增加一个中间层来解决。因此中间码应用而生,即字节码(Bytecode).Java所有的指令有200个左右,一个字节(8位)可以存储256种不同的指令信息,一个这样的字节码就称为字节码(Bytecode)。在代码的执行过程中,JVM将字节码解释执行,屏蔽对底层操作系统的依赖;JVM也可以将字节码编译执行,如果是热点代码,会通过JIT动态的编译为机器码,提高执行效率。如下图所示,十六进制表示的的二进制流通常是一个操作指令。起始的4个字节非常特殊,及绿色框的cafe babe(十六进制)是Gosling定义的一个魔法数,意思的Cafe Baby, 其十进制为3405691582。 它的作用:标志该文件是一个Java类文件,如果没有识别到该标志,说明该文件不是Java类文件或者文件已经损坏,无法进行加载。而红色框代码着版本号,0x37 十进制为55,是JDK11的内部版本号。


    类的二进制字节码

      纯数字的字节码阅读起来向天书一样难,当初汇编语言为了改进机器语言,使用助记符来代码数字指令。JVM在字节码上也设计了一套操作码助记符,使用特殊单词来标记这些数字。如ICONST_0代表00000001,即十六进制数为0x03;ALOAD_0代表00101010,即 0x2a; POP 代 表 01010111,即0x57。ICONST和ALOAD 的首字母表示具体的数据类型,如A代表引用类型变量,I代表int类型相关操作,其他类型均是其类型的首字母,例如 FLOAD_0、LLOAD_0 FCONST_0等。字节码主要指令如下。

    1. 加载或存储指令

    在某个栈帧中,通过指令操作数据在虚拟机栈的局部变量表与操作栈之间来回传输,常见指令如下:

    1. 将局部变量加载到操作栈中。如 LOAD(将 int 型的局部变量压入栈和ALOAD ( 将对象引用的局部变量压入栈)等。
    2. 从操作栈顶存储到局部变量表。如 ISTORE、ASTORE等。
    3. 将常加载到操作栈顶,这是极为高频使用的指令。如ICONST、BIPUSH、SIPUSH、LDC 等。
    • ICONST加载的是-1~5的数(ICONST与 BIPUSH 的加载界限)。
    • BIPUSH,即 Byte Immediate PUSH,加载 -128 ~ 127 之间的数。
    • SIPUSH,即 Short Immediate PUSH,加载 -32768 ~ 32767 之间的数。
    • LDC,即 Load Constant,在-2147483648 ~ 2147483647 或者是字符串时JVM 采用LDC 指令压入栈中。
    //BIPUSH-2,在-1至5之外的数字使用BIPUSH指令加载
    int a = -2;
    //ICONST_M1 // -1,直接使用 ICONST 加的最小值
    int b = -1;
    //ICONST_0
    int c = 0;
    //SIPUSH 20000
    int e = 20000;
    //LDC 40000
    int f = 40000;
    
    2. 运算指令

    对两个操作栈帧上的值进行运算,并把结果写入操作栈顶,如IADD、IMUL等

    3. 类型转换指令

    显式转换两种不同的数值类型。如I2L、D2F 等。

    4. 对象创建与访问指令

    根据类进行对象的创建、初始化、方法调用相关指令,常见指令如下

    1. 创建对象指令。如NEW、NEWARRAY等。
    2. 访问属性指令。如GETFIELD、PUTFIELD、GETSTATIC 等
    3. 检查实例类型指令。如INSTANCEOF、CHECKCAST 等。
    5. 操作栈管理指令

    JVM 提供了直接控制操作栈的指令,常见指令如下

    1. 出栈操作。如POP 即一个元素,POP2 即两个元素
    2. 复制栈顶元素并压入栈。如 DUP。
    6. 方法调用与返回指令

    常见指令如下:

    1. INVOKEVIRTUAL 指令:调用对象的实例方法。
    2. INVOKESPECIAL 指令:调用实例初始化方法、私有方法、父类方法等
    3. INVOKESTATIC 指令:调用类静态方法。
    4. RETURN 指令: 返回VOID 类型
    7. 方法调用与返回指令

    JVM使用方法结构中的ACC_SYNCHRONIZED标志同步方法,指令集中有MONITORENTER和MONITOREXIT支持 synchronized 语义。
    除字节码指令外,还包含一些额外信息。例如,LINENUMBER存储了字节码与源码行号的对应关系,方便调试的时候正确地定位到代码的所在行;LOCALVARIABLE存储当前方法中使用到的局部变量表。
    我们编写好的.java 文件是源代码文件,并不能交给机器直接执行,需要将其编译成为字节码甚至是机器码文件。那么静态编译器如何把源码转化成字节码呢?如下图


    源码转字节码

    词法解析是通过空格分隔出单词、操作符、控制符等信息,将其形成token信息流,传递给语法解析器:在语法解析时,把词法解析得到的token信息流按照Java语法规则组装成一棵语法树,如上图虚线框所示,在语义分析阶段,需要检查关键字的使用是否合理、类型是否匹配、作用域是否正确等;当语义分析完成之后,即可生成字节码字节码必须通过类加载过程加载到JVM环境后,才可以执行。执行有三种模第一,解释执行;第二,JIT 编译执行,第三,JIT 编译与解释混合执行(主流J默认执行模式)。混合执行模式的优势在于解释器在启动时先解释执行,省去编译时间随着时间推进,JVM 通过热点代码统计分析,识别高频的方法调用、循环体、公共模块等,基于强大的JIT 动态编译技术,将热点代码转换成机器码,直接交给 CPU执行。JIT的作用是将Java 字节码动态地编译成可以直接发送给处理器指令执行的机器码。简要流程如下图所示。


    即时编译流程

    相关文章

      网友评论

          本文标题:走进JVM-字节码

          本文链接:https://www.haomeiwen.com/subject/qgfnidtx.html