Java基础知识(21)- Java 虚拟机(JVM)

 

1. JVM 简介

    Java 平台由 Java 虚拟机(Java Virtual Machine,JVM)和 Java 应用编程接口(Application Programming Interface,API)构成。

    Java 应用编程接口是一套独立于操作系统的标准接口,可分为基本部分和扩展部分。在硬件或操作系统平台上安装一个 Java 平台之后,Java 应用程序就可运行。

    Java 虚拟机(Java Virtual Machine,JVM)是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。

    JVM 屏蔽了与操作系统平台相关的信息,使得 Java 程序只需要生成在 Java 虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是 Java 能够 “一次编译,到处运行” 的原因。

2. JRE、JVM 和 JDK 的关系

    JRE(Java Runtime Environment,Java 运行环境)是 Java 平台,所有的程序都要在 JRE 下才能够运行。包括 JVM 和 Java 核心类库和支持文件。JRE 主要由 Java API 和 JVM 组成,提供了用于执行 java 应用程序最低要求的环境。

    JVM(Java Virtual Machine,Java虚拟机)是 JRE 的一部分。JVM 主要工作是解释自己的指令集(即字节码)并映射到本地的 CPU 指令集和 OS 的系统调用。Java 语言是跨平台运行的,不同的操作系统会有不同的 JVM 映射规则,使之与操作系统无关,完成跨平台性。

    JDK(Java Development Kit,Java 开发工具包)是用来编译、调试 Java 程序的开发工具包。包括Java工具(javac/java/jdb 等)和 Java 基础的类库(java API )。JDK 是 Programming tools、JRE 和 JVM 的一个集合。

3. JVM体系结构

    1) 类加载子系统

        (1) 加载类的过程

            a) 装载(loading)

                负责找到二进制字节码并加载至JVM中,JVM 通过类名、类所在的包名、ClassLoader 完成类的加载。因此,标识一个被加载了的类:类名 + 包名 + ClassLoader 实例ID。

            b) 链接(linking)

                负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口。

                完成校验后,JVM初始化类中的静态变量,并将其赋值为默认值。

                最后对比类中的所有属性、方法进行验证,以确保要调用的属性、方法存在,以及具备访问权限(例如private、public等),否则会造成NoSuchMethodError、NoSuchFieldError等错误信息。

            c) 初始化(initializing)

                负责执行类中的静态初始化代码、构造器代码以及静态属性的初始化,以下四种情况初始化过程会被触发。
    
        (2) 委派模式(Delegation Mode)

            当 JVM 加载一个类的时候,下层的加载器会将任务给上一层类加载器,上一层加载检查它的命名空间中是否已经加载这个类,如果已经加载,直接使用这个类。如果没有加载,继续往上委托直到顶部。检查之后,按照相反的顺序进行加载。如果Bootstrap加载器不到这个类,则往下委托,直到找到这个类。一个类可以被不同的类加载器加载。

            可见性限制:下层的加载器能够看到上层加载器中的类,反之则不行,委派只能从下到上。

            不允许卸载类:类加载器可以加载一个类,但不能够卸载一个类。但是类加载器可以被创建或者删除。


    2) 运行时数据区

        (1) PC寄存器(或PC程序计数器)

            JVM 中的PC寄存器是对物理PC寄存器的一种抽象模拟。

            PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令

        (2) JVM栈

            JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放当前线程中局部基本类型的变量(Java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及 Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆的地址。

        (3) 堆(Heap)

            堆是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过 new 创建的对象的内存都在此分配,Heap 中的对象的内存需要等待 GC 进行回收。

            堆在 JVM 启动的时候就被创建,堆中储存了各种对象,这些对象被自动管理内存系统(Automatic Storage Management System),也就是常说的 “Garbage Collector(垃圾回收器)” 管理。这些对象无需、也无法显示地被销毁。

            堆是 JVM 中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,导致 new 对象的开销比较大。

            JVM 将 Heap 分为两块:新生代 New Generation 和旧生代 Old Generation

        (4) 方法区域(Method Area)

            在Sun JDK中这块区域对应的为 Permanet Generation,又称为持久代。

            方法区域存放所加载类的信息(名称、修饰符等)、类中的静态变量、类中定义为 final 类型的常量、类中的 Field 信息、类中的方法信息,当开发人员在程序中通过 Class 对象中的 getName,isInstance 等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定条件下它也会被 GC,当方法区域需要使用的内存超过其允许的大小时,就会抛出 OutOfMemory 的错误信息。

        (5) 运行时常量池(Runtime Constant Pool)

            存放的为类中的固定常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

        (6) 本地方法堆栈(Native Method Stacks)

            JVM 采用本地方法堆来支持native方法的执行,此区域用于存储每个native方法调用的状态。

    3) 执行引擎

        (1) 字节码解释器

            是一种电脑程序,能够把高级编程语言一行一行直接翻译运行。解释器不会一次把整个程序翻译出来,只像一位“中间人”,每次运行程序时都要先转成另一种语言再作运行,因此解释器的程序运行速度比较缓慢。它每翻译一行程序叙述就立刻运行,然后再翻译下一行,再运行,如此不停地进行下去。它会先将源码翻译成另一种语言,以供多次运行而无需再经编译。其制成品无需依赖编译器而运行,程序运行速度比较快。

        (2) 即时编译器 (Just In Time Compiler,JIT编译器)

            即时编译器 (Just In Time Compiler,JIT编译器),是指一种在运行时期把字节码编译成原生机器码的技术,一句一句翻译源代码,但是会将翻译过的代码缓存起来以降低性能耗损。这项技术是被用来改善虚拟机的性能的。

            在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器。

            即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有限定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。

        (3) 垃圾回收器

            垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。


4. JVM生命周期

    Java实例对应一个独立运行的Java程序(进程级别)

    1) 启动

        启动一个 Java 程序,一个 JVM 实例就产生。拥有 public static void main(String[] args) 函数的 class 可以作为 JVM 实例运行的起点。

    2) 运行

        main() 作为程序初始线程的起点,任何其他线程均可由该线程启动。

        JVM 内部有两种线程:守护线程和非守护线程。

        main() 属于非守护线程,守护线程通常由 JVM 使用,程序可以指定创建的线程为守护线程。

    3) 消亡

        当程序中的所有非守护线程都终止时,JVM 才退出;若安全管理器允许,程序也可以使用 Runtime 类或者 System.exit() 来退出。

        JVM执行引擎实例则对应了属于用户运行程序线程它是线程级别的。

5. JVM指令集

    指令码    助记符         说明
    0×00    nop             什么都不做
    0×01    aconst_null   将null推送至栈顶
    0×02    iconst_m1    将int型-1推送至栈顶
    0×03    iconst_0     将int型0推送至栈顶
    0×04    iconst_1     将int型1推送至栈顶
    0×05    iconst_2    将int型2推送至栈顶
    0×06    iconst_3    将int型3推送至栈顶
    0×07    iconst_4    将int型4推送至栈顶
    0×08    iconst_5    将int型5推送至栈顶
    0×09    lconst_0    将long型0推送至栈顶
    0x0a    lconst_1     将long型1推送至栈顶
    0x0b    fconst_0    将float型0推送至栈顶
    0x0c    fconst_1     将float型1推送至栈顶
    0x0d    fconst_2    将float型2推送至栈顶
    0x0e    dconst_0    将double型0推送至栈顶
    0x0f    dconst_1    将double型1推送至栈顶
    0×10    bipush        将单字节的常量值(-128~127)推送至栈顶
    0×11    sipush        将一个短整型常量值(-32768~32767)推送至栈顶
    0×12    ldc            将int, float或String型常量值从常量池中推送至栈顶
    0×13    ldc_w        将int, float或String型常量值从常量池中推送至栈顶(宽索引)
    0×14    ldc2_w        将long或double型常量值从常量池中推送至栈顶(宽索引)
    0×15    iload        将指定的int型本地变量推送至栈顶
    0×16    lload        将指定的long型本地变量推送至栈顶
    0×17    fload        将指定的float型本地变量推送至栈顶
    0×18    dload        将指定的double型本地变量推送至栈顶
    0×19    aload        将指定的引用类型本地变量推送至栈顶
    0x1a    iload_0        将第0个int型本地变量推送至栈顶
    0x1b    iload_1        将第1个int型本地变量推送至栈顶
    0x1c    iload_2        将第2个int型本地变量推送至栈顶
    0x1d    iload_3        将第3个int型本地变量推送至栈顶
    0x1e    lload_0        将第0个long型本地变量推送至栈顶
    0x1f    lload_1        将第1个long型本地变量推送至栈顶
    0×20    lload_2        将第2个long型本地变量推送至栈顶
    0×21    lload_3        将第3个long型本地变量推送至栈顶
    0×22    fload_0        将第0个float型本地变量推送至栈顶
    0×23    fload_1        将第1个float型本地变量推送至栈顶
    0×24    fload_2        将第2个float型本地变量推送至栈顶
    0×25    fload_3        将第3个float型本地变量推送至栈顶
    0×26    dload_0        将第0个double型本地变量推送至栈顶
    0×27    dload_1        将第1个double型本地变量推送至栈顶
    0×28    dload_2        将第2个double型本地变量推送至栈顶
    0×29    dload_3        将第3个double型本地变量推送至栈顶
    0x2a    aload_0        将第0个引用类型本地变量推送至栈顶
    0x2b    aload_1        将第1个引用类型本地变量推送至栈顶
    0x2c    aload_2        将第2个引用类型本地变量推送至栈顶
    0x2d    aload_3        将第3个引用类型本地变量推送至栈顶
    0x2e    iaload        将int型数组指定索引的值推送至栈顶
    0x2f    laload        将long型数组指定索引的值推送至栈顶
    0×30    faload        将float型数组指定索引的值推送至栈顶
    0×31    daload        将double型数组指定索引的值推送至栈顶
    0×32    aaload        将引用型数组指定索引的值推送至栈顶
    0×33    baload        将boolean或byte型数组指定索引的值推送至栈顶
    0×34    caload        将char型数组指定索引的值推送至栈顶
    0×35    saload        将short型数组指定索引的值推送至栈顶
    0×36    istore        将栈顶int型数值存入指定本地变量
    0×37    lstore        将栈顶long型数值存入指定本地变量
    0×38    fstore        将栈顶float型数值存入指定本地变量
    0×39    dstore        将栈顶double型数值存入指定本地变量
    0x3a    astore        将栈顶引用型数值存入指定本地变量
    0x3b    istore_0    将栈顶int型数值存入第0个本地变量
    0x3c    istore_1    将栈顶int型数值存入第1个本地变量
    0x3d    istore_2    将栈顶int型数值存入第2个本地变量
    0x3e    istore_3    将栈顶int型数值存入第3个本地变量
    0x3f    lstore_0    将栈顶long型数值存入第0个本地变量
    0×40    lstore_1    将栈顶long型数值存入第1个本地变量
    0×41    lstore_2    将栈顶long型数值存入第2个本地变量
    0×42    lstore_3    将栈顶long型数值存入第3个本地变量
    0×43    fstore_0    将栈顶float型数值存入第0个本地变量
    0×44    fstore_1    将栈顶float型数值存入第1个本地变量
    0×45    fstore_2    将栈顶float型数值存入第2个本地变量
    0×46    fstore_3    将栈顶float型数值存入第3个本地变量
    0×47    dstore_0    将栈顶double型数值存入第0个本地变量
    0×48    dstore_1    将栈顶double型数值存入第1个本地变量
    0×49    dstore_2    将栈顶double型数值存入第2个本地变量
    0x4a    dstore_3    将栈顶double型数值存入第3个本地变量
    0x4b    astore_0    将栈顶引用型数值存入第0个本地变量
    0x4c    astore_1    将栈顶引用型数值存入第1个本地变量
    0x4d    astore_2    将栈顶引用型数值存入第2个本地变量
    0x4e    astore_3    将栈顶引用型数值存入第3个本地变量
    0x4f    iastore        将栈顶int型数值存入指定数组的指定索引位置
    0×50    lastore        将栈顶long型数值存入指定数组的指定索引位置
    0×51    fastore        将栈顶float型数值存入指定数组的指定索引位置
    0×52    dastore        将栈顶double型数值存入指定数组的指定索引位置
    0×53    aastore        将栈顶引用型数值存入指定数组的指定索引位置
    0×54    bastore        将栈顶boolean或byte型数值存入指定数组的指定索引位置
    0×55    castore        将栈顶char型数值存入指定数组的指定索引位置
    0×56    sastore        将栈顶short型数值存入指定数组的指定索引位置
    0×57    pop            将栈顶数值弹出 (数值不能是long或double类型的)
    0×58    pop2        将栈顶的一个(long或double类型的)或两个数值弹出(其它)
    0×59    dup            复制栈顶数值并将复制值压入栈顶
    0x5a    dup_x1        复制栈顶数值并将两个复制值压入栈顶
    0x5b    dup_x2        复制栈顶数值并将三个(或两个)复制值压入栈顶
    0x5c    dup2        复制栈顶一个(long或double类型的)或两个(其它)数值并将复制值压入栈顶
    0x5d    dup2_x1        <待补充>
    0x5e    dup2_x2        <待补充>
    0x5f    swap        将栈最顶端的两个数值互换(数值不能是long或double类型的)
    0×60    iadd        将栈顶两int型数值相加并将结果压入栈顶
    0×61    ladd        将栈顶两long型数值相加并将结果压入栈顶
    0×62    fadd        将栈顶两float型数值相加并将结果压入栈顶
    0×63    dadd        将栈顶两double型数值相加并将结果压入栈顶
    0×64    isub        将栈顶两int型数值相减并将结果压入栈顶
    0×65    lsub        将栈顶两long型数值相减并将结果压入栈顶
    0×66    fsub        将栈顶两float型数值相减并将结果压入栈顶
    0×67    dsub        将栈顶两double型数值相减并将结果压入栈顶
    0×68    imul        将栈顶两int型数值相乘并将结果压入栈顶
    0×69    lmul        将栈顶两long型数值相乘并将结果压入栈顶
    0x6a    fmul        将栈顶两float型数值相乘并将结果压入栈顶
    0x6b    dmul        将栈顶两double型数值相乘并将结果压入栈顶
    0x6c    idiv        将栈顶两int型数值相除并将结果压入栈顶
    0x6d    ldiv        将栈顶两long型数值相除并将结果压入栈顶
    0x6e    fdiv        将栈顶两float型数值相除并将结果压入栈顶
    0x6f    ddiv        将栈顶两double型数值相除并将结果压入栈顶
    0×70    irem        将栈顶两int型数值作取模运算并将结果压入栈顶
    0×71    lrem        将栈顶两long型数值作取模运算并将结果压入栈顶
    0×72    frem        将栈顶两float型数值作取模运算并将结果压入栈顶
    0×73    drem        将栈顶两double型数值作取模运算并将结果压入栈顶
    0×74    ineg        将栈顶int型数值取负并将结果压入栈顶
    0×75    lneg        将栈顶long型数值取负并将结果压入栈顶
    0×76    fneg        将栈顶float型数值取负并将结果压入栈顶
    0×77    dneg        将栈顶double型数值取负并将结果压入栈顶
    0×78    ishl        将int型数值左移位指定位数并将结果压入栈顶
    0×79    lshl        将long型数值左移位指定位数并将结果压入栈顶
    0x7a    ishr        将int型数值右(符号)移位指定位数并将结果压入栈顶
    0x7b    lshr        将long型数值右(符号)移位指定位数并将结果压入栈顶
    0x7c    iushr        将int型数值右(无符号)移位指定位数并将结果压入栈顶
    0x7d    lushr        将long型数值右(无符号)移位指定位数并将结果压入栈顶
    0x7e    iand        将栈顶两int型数值作“按位与”并将结果压入栈顶
    0x7f    land        将栈顶两long型数值作“按位与”并将结果压入栈顶
    0×80    ior            将栈顶两int型数值作“按位或”并将结果压入栈顶
    0×81    lor            将栈顶两long型数值作“按位或”并将结果压入栈顶
    0×82    ixor        将栈顶两int型数值作“按位异或”并将结果压入栈顶
    0×83    lxor        将栈顶两long型数值作“按位异或”并将结果压入栈顶
    0×84    iinc        将指定int型变量增加指定值,可以有两个变量,分别表示index, const,index指第index个int型本地变量,const增加的值
    0×85    i2l            将栈顶int型数值强制转换成long型数值并将结果压入栈顶
    0×86    i2f            将栈顶int型数值强制转换成float型数值并将结果压入栈顶
    0×87    i2d            将栈顶int型数值强制转换成double型数值并将结果压入栈顶
    0×88    l2i            将将栈顶long型数值强制转换成int型数值并将结果压入栈顶
    0×89    l2f            将将栈顶long型数值强制转换成float型数值并将结果压入栈顶
    0x8a    l2d            将将栈顶long型数值强制转换成double型数值并将结果压入栈顶
    0x8b    f2i            将将栈顶float型数值强制转换成int型数值并将结果压入栈顶
    0x8c    f2l            将将栈顶float型数值强制转换成long型数值并将结果压入栈顶
    0x8d    f2d            将将栈顶float型数值强制转换成double型数值并将结果压入栈顶
    0x8e    d2i            将将栈顶double型数值强制转换成int型数值并将结果压入栈顶
    0x8f    d2l            将将栈顶double型数值强制转换成long型数值并将结果压入栈顶
    0×90    d2f            将将栈顶double型数值强制转换成float型数值并将结果压入栈顶
    0×91    i2b            将将栈顶int型数值强制转换成byte型数值并将结果压入栈顶
    0×92    i2c            将将栈顶int型数值强制转换成char型数值并将结果压入栈顶
    0×93    i2s            将将栈顶int型数值强制转换成short型数值并将结果压入栈顶
    0×94    lcmp        比较栈顶两long型数值大小,并将结果(1,0,-1)压入栈顶
    0×95    fcmpl        比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
    0×96    fcmpg        比较栈顶两float型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
    0×97    dcmpl        比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将-1压入栈顶
    0×98    dcmpg        比较栈顶两double型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为NaN时,将1压入栈顶
    0×99    ifeq        当栈顶int型数值等于0时跳转
    0x9a    ifne        当栈顶int型数值不等于0时跳转
    0x9b    iflt        当栈顶int型数值小于0时跳转
    0x9c    ifge        当栈顶int型数值大于等于0时跳转
    0x9d    ifgt        当栈顶int型数值大于0时跳转
    0x9e    ifle        当栈顶int型数值小于等于0时跳转
    0x9f    if_icmpeq    比较栈顶两int型数值大小,当结果等于0时跳转
    0xa0    if_icmpne    比较栈顶两int型数值大小,当结果不等于0时跳转
    0xa1    if_icmplt    比较栈顶两int型数值大小,当结果小于0时跳转
    0xa2    if_icmpge    比较栈顶两int型数值大小,当结果大于等于0时跳转
    0xa3    if_icmpgt    比较栈顶两int型数值大小,当结果大于0时跳转
    0xa4    if_icmple    比较栈顶两int型数值大小,当结果小于等于0时跳转
    0xa5    if_acmpeq    比较栈顶两引用型数值,当结果相等时跳转
    0xa6    if_acmpne    比较栈顶两引用型数值,当结果不相等时跳转
    0xa7    goto        无条件跳转
    0xa8    jsr            跳转至指定16位offset位置,并将jsr下一条指令地址压入栈顶
    0xa9    ret            返回至本地变量指定的index的指令位置(一般与jsr, jsr_w联合使用)
    0xaa    tableswitch    用于switch条件跳转,case值连续(可变长度指令)
    0xab    lookupswitch    用于switch条件跳转,case值不连续(可变长度指令)
    0xac    ireturn        从当前方法返回int
    0xad    lreturn        从当前方法返回long
    0xae    freturn        从当前方法返回float
    0xaf    dreturn        从当前方法返回double
    0xb0    areturn        从当前方法返回对象引用
    0xb1    return        从当前方法返回void
    0xb2    getstatic    获取指定类的静态域,并将其值压入栈顶
    0xb3    putstatic    为指定的类的静态域赋值
    0xb4    getfield    获取指定类的实例域,并将其值压入栈顶
    0xb5    putfield    为指定的类的实例域赋值
    0xb6    invokevirtual    调用实例方法
    0xb7    invokespecial    调用超类构造方法,实例初始化方法,私有方法
    0xb8    invokestatic    调用静态方法
    0xb9    invokeinterface    调用接口方法
    0xba    –
    0xbb    new            创建一个对象,并将其引用值压入栈顶
    0xbc    newarray    创建一个指定原始类型(如int, float, char…)的数组,并将其引用值压入栈顶
    0xbd    anewarray    创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
    0xbe    arraylength    获得数组的长度值并压入栈顶
    0xbf    athrow        将栈顶的异常抛出
    0xc0    checkcast    检验类型转换,检验未通过将抛出ClassCastException
    0xc1    instanceof    检验对象是否是指定的类的实例,如果是将1压入栈顶,否则将0压入栈顶
    0xc2    monitorenter    获得对象的锁,用于同步方法或同步块
    0xc3    monitorexit    释放对象的锁,用于同步方法或同步块
    0xc4    wide        当本地变量的索引超过255时使用该指令扩展索引宽度。
    0xc5    multianewarray    create a new array of dimensions dimensions with elements of type identified by class reference in constant pool index (indexbyte1 << 8 + indexbyte2); the sizes of each dimension is identified by count1, [count2, etc.]
    0xc6    ifnull        If value is null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)
    0xc7    ifnonnull    If value is not null, branch to instruction at branchoffset (signed short constructed from unsigned bytes branchbyte1 << 8 + branchbyte2)
    0xc8    goto_w        Goes to another instruction at branchoffset (signed int constructed from unsigned bytes branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4)
    0xc9    jsr_w           Jump to subroutine at branchoffset (signed int constructed from unsigned bytes branchbyte1 << 24 + branchbyte2 << 16 + branchbyte3 << 8 + branchbyte4) and place the return address on the stack
    0xca    breakpoint    Reserved for breakpoints in Java debuggers; should not appear in any class file
    0xcb-0xfd    未命名    These values are currently unassigned for opcodes and are reserved for future use
    0xfe    impdep1        Reserved for implementation-dependent operations within debuggers; should not appear in any class file
    0xff    impdep2        Reserved for implementation-dependent operations within debuggers; should not appear in any class file

    以上指令集表,方便查看Java字节码文件。

 

posted @ 2022-03-13 16:14  垄山小站  阅读(362)  评论(0)    收藏  举报