精华内容
下载资源
问答
  • 字节码指令看重写在JVM中的实现

    万次阅读 2015-07-30 19:22:35
    面向对象语言的继承、封装和多态的特性,在JVM中是怎样进行编译、解析,以及通过字节码指令如何确定方法调用的版本是本文如下要探讨的主要内容,全文围绕一个多态的简单举例来看在JVM中是如何实现的。

            Java是解释执行的,包括动态链接的特性,都给解析或运行期间提供了很多灵活扩展的空间。面向对象语言的继承、封装和多态的特性,在JVM中是怎样进行编译、解析,以及通过字节码指令如何确定方法调用的版本是本文如下要探讨的主要内容,全文围绕一个多态的简单举例来看在JVM中是如何实现的。

            先简单介绍几个概念。对于字节码执行模及字节码指令集的相关概念可以参考之前的一篇介绍:JVM字节码执行模型及字节码指令集

    一、方法调用的解析

            在Class文件中,方法调用是常量池中的一个符号引用,在加载的解析期或者运行时才能确定直接引用。

            下面两种指令是在解析期就可以确定直接引用,调用的对应方法也叫作非虚方法。

    1、  invokestatic 主要用于调用静态方法

    2、  invokespecial 主要用于调用私有方法,构造器,父类方法。

            下面两种是在运行时才能确定直接引用的,但是除了final方法,final方法也可以在解析期确定方法的调用版本。

    1、  invokevirtual 虚方法,不确定调用那一个实现类

    2、  invokeinterface 接口方法,运行时才能确定实现接口的对象。

     

    二、动态分派

              先回顾一下静态和动态分派的概念。Java中,所有以静态类型来定位方法执行版本的分派动作,都称为静态分派。其实也就是重载(Overload)就是一种典型的静态分派,在编译期就可以知道方法调用的实际版本。相对得,动态分派是需要在运行期才能确定方法的版本,也就是直接引用,一种典型应用就是重写(OverWrite)。在调用invokevirtual指令时,把常量池中的类方法符号引用解析到直接引用的过程就是重写的过程,运行期根据实际类型确定方法的执行版本。

    三、解释执行

             Java的解释执行机制,使jaavc编译的过程涵盖了从程序代码的语法、词法分析,再到AST(抽象语法树)生成线性的字节码指令流的过程。而解释执行是在JVM内部,基于栈的指令集提供了整个平台的可移植性支撑,所以这也是java执行慢的要因,因为不像编译执行,过程中需要更多的出入栈指令,而栈又是内存的一个区块,对内存的频繁访问降低了性能。

    四、重写(OverWrite)举例

              下面通过一个简单的例子来看一下重写在JVM中的字节码执行模型。

    父类:

    package bytecode;
    
    /**
     * Created by yunshen.ljy on 2015/7/27.
     */
    public class Wine {
    
        public String drink(int ml) {
            return "drink " + ml + "ml wine";
        }
    
    }
    

    子类:

    package bytecode;
    /**
     * Created by yunshen.ljy on 2015/7/27.
     */
    public class Beer extends Wine{
    
        /**
         * 重写父类方法,实现多态
         */
        public String drink(int ml){
            return "drink " + ml +"ml beer";
        }
    }
    

    调用:

    package bytecode;
    
    /**
     * Created by yunshen.ljy on 2015/7/26.
     */
    public class MethodInvotionTest {
    
        public String drink(int ml) {
    
            Wine wines = new Beer();
            return wines.drink(ml);
    
        }
    }
    

            我们都知道,方法调用返回的结果是“drink XX ml beer”。但是在编译期,字节码中的指令是无法确定实际调用的方法版本的。下面看一下调用方法的字节码结构。

    public java.lang.String drink(int);
        descriptor: (I)Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=2
             0: new           #2                  // class bytecode/Beer
             3: dup
             4: invokespecial #3                  // Method bytecode/Beer."<init>":()V
             7: astore_2
             8: aload_2
             9: iload_1
            10: invokevirtual #4                  // Method bytecode/Wine.drink:(I)Ljava/lang/String;
            13: areturn
          LineNumberTable:
            line 10: 0
            line 12: 8
    

             第一条指令new 创建对象,把引用入栈指令,new 指令后面的#4就是前文提到的,对于运行时常量池的一个引用,只是javap命令处理成比较易懂的方式来显示。接着,后面的dup 指令复制刚放入的引用(操作数栈栈顶的值的复制并且将这个“副本”放到栈顶)。Invokespecial 指令就是之前介绍的非虚方法调用指令,在操作数栈中通过其中的一个引用调用Beer的构造器,初始化对象,让另一个相同引用指向初始化的对象,然后前一个引用(this)弹出栈。astore_2把引用保存到局部变量表中的索引2位置中。aload_2 把刚才局部变量表中索引2处的值压入操作栈。iload_1将int参数,也就是局部变量表中索引1处的值压入操作数栈。Invokevirtual指令将执行操作数栈中的两个值出栈,执行方法调用,指令后的#4只是常量池中的一个符号引用,只有在运行时,才能确定方法的直接引用。最后的areturn语句将方法执行结果的值出栈,消除当前的栈帧,如果上层有对于当前方法的继续调用,那么会将调用的方法的栈帧设置成当前栈帧(current stack frame)。

           下面通过对比每条指令执行后,局部变量表和操作数栈中的值来加强一下上述的出栈、入栈操作。首先通过stack=2, locals=3, args_size=2 ,先知道了方法的局部变量表是占用了3个slot,操作数栈的size是2个slot。而因为我们的方法是实例方法(非类方法),所以隐含的this关键字的指令我们先描述到局部变量表的第一个位置,占用一个slot。(以下不再画出常量池的结构,可参照calss文件自行分析)

     








            这里只是个简单的举例,和一些知识的回顾。知道JVM的执行引擎和字节码指令的一些概念,会让我们对于程序执行的结果预期更加准确,也更易理解一些java语言版本的设计模式实现。这里只是抛砖引玉,对于其他几种方法调用的指令,也建议大家尝试进行更多的分析。

    展开全文
  • Java字节码

    千次阅读 2019-01-14 13:23:40
    Java最黑科技的玩法就是字节码编程,也就是动态修改或是动态生成 Java 字节码。使用字节码可以玩出很多高级的玩法,最高级的还是在 Java 程序运行时进行字节码修改和代码注入。听起来是不是一些很黑客,也很黑科技的...

    Java最黑科技的玩法就是字节码编程,也就是动态修改或是动态生成 Java 字节码。使用字节码可以玩出很多高级的玩法,最高级的还是在 Java 程序运行时进行字节码修改和代码注入。听起来是不是一些很黑客,也很黑科技的事?是的,这个方式使用 Java 这门静态语言在运行时可以进行各种动态的代码修改,而且可以进行无侵入的编程。
    比如,我们不需要在代码中埋点做统计或监控,可以使用这种技术把我们的监控代码直接以字节码的方式注入到别人的代码中,从而实现对实际程序运行情况进行统计和监控。但是要做到这个事,还需要学习一个叫 Java Agent 的技术(可以参考我的这篇文章:Java Agent)。

    1、Class类文件的结构

    根据 Java 虚拟机规范的规定,Class 文件格式采用一种类似 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表
    无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4个字节和 8 个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成字符串值。
    是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件本质上就是一张表,它由下表所示的数据项构成。

    类型 名称 数量 描述
    u4 magic 1 魔数,占用4个字节,offset 0-3
    u2 minor_version 1 次版本号,offset 4-5
    u2 major_version 1 主版本号 ,offset 7-8
    u2 constant_pool_count 1 常量池数量,offset 8-9
    cp_info constant_poll constant_pool_count-1 常量池,Class 文件之中的资源仓库。数量不固定
    u2 access_flags 1 访问标志,用于识别一些类或者接口层次的访问信息
    u2 this_class 1 类索引,用于确定这个类的全限定名
    u2 super_class 1 父类索引,用于确定这个类的父类的全限定名
    u2 interfaces_count 1 接口计数器,表示索引表的容量
    u2 interfaces interfaces_count 接口索引集合,用来描述这个类实现了哪些接口
    u2 fields_count 1 字段容量计数器,记录这个表含有多少个字段
    field_info fields fields_count 字段表集合,用于描述接口或者类中声明的变量
    u2 methods_count 1 方法表计数器,记录这个表含有多少个方法
    method_info methods methods_count 方法表集合,存放 Class 的方法集合
    u2 attributes_count 1 属性表计数器,表示字段表或方法表有多少个属性
    attribute attributes attributes_count 属性表集合,在 Class 文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息

    为方便讲解,在这里准备了一段最简单的代码,也希望大家能跟着实际操作一遍:

    package org.clazz;
    
    public class TestClazz {
        private int m;
    
        public int inc() {
            return m + 1;
        }
    }
    

    使用 javac 将这个文件转换成 Class,然后用十六进制编辑器 WinHex 打开这个 Class 文件:
    在这里插入图片描述
    有了以上的知识准备,现在我们一起分析上面的 Class 分别代表什么意思。揭开这层神秘的面纱!

    1.1、魔数

    每个 Class 文件的头 4 个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的 Class 文件。
    很多文件存储标准中都使用魔数来进行身份识别,譬如图片格式,如 gif 或者 jpeg 等在文件头中都存有魔数。
    文件格式的制定者可以自由地选择魔数值,只要这个魔数值还没有被广泛采用过同时又不会引起混淆即可。
    我们看到 TestClazz.class 的魔数也就是头 4 个字节为 CA FE BA BE,用十六进制表示是 0xCAFEBABE(咖啡宝贝?这个名称也太浪漫了吧)。这也意味着每个 Class 文件的魔数值都必须为 0xCAFEBABE。
    在这里插入图片描述

    1.2、Class 文件的版本

    紧接着魔数的 4 个字节存储的是 Class 文件的版本号:5-6 个字节是次版本号(Minor Version),7-8 个字节是主版本号(Major Version)。Java 的版本号是从 45 开始的,JDK 1.1 之后的每个JDK 大版本发布主版本号加 1,高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过起把那本号的 Class 文件。
    Class 文件版本号:

    JDK版本号 10进制版本号 16进制版本号
    1.1 45.0 00 00 00 2D
    1.2 46.0 00 00 00 2E
    1.3 47.0 00 00 00 2F
    1.4 48.0 00 00 00 30
    1.5 49.0 00 00 00 31
    1.6 50.0 00 00 00 32
    1.7 51.0 00 00 00 33
    1.8 52.0 00 00 00 34

    再看看文件对应的值:
    在这里插入图片描述
    我们看到代表主版本号的 7-8 个字节的值为 0x0034,也即十进制的 52,该版本号说明这个文件是可以被 JDK 1.8 或以上版本虚拟机执行的 Class 文件。

    1.3、常量池

    紧接着主次版本号之后的是常量池入口,常量池可以理解为 Class 文件之中的资源仓库,它是占用 Class 文件空间最大的数据项目之一。
    由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项 u2 类型的数据,代表常量池容量计数值(constant_pool_count)。
    在这里插入图片描述
    如上图所示,常量池容量为十六进制数 0x0016,即十进制的 19,结合上面的 Class 表,我们能知道常量池中有 19 - 1 = 18 项常量。
    常量池容量计数值之后就是常量池,常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
    字面量比较接近于 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。
    符号引用则属于编译原理方面的概念,包括了下面三类常量:

    • 类和接口的全限定名(Fully Qualified Name)
    • 字段的名称和描述符(Descriptor)
    • 方法的名称和描述符

    常量池中每一项常量都是一个表,总共 14 种表:

    类型 标志 描述
    CONSTANT_Utf8_info 1 UTF-8 编码的字符串
    CONSTANT_Integer_info 3 整型字面量
    CONSTANT_Float_info 4 浮点型字面量
    CONSTANT_Long 5 长整型字面量
    CONSTANT_Double_info 6 双精度浮点型字面量
    CONSTANT_Class_info 7 类或接口的符号引用
    CONSTANT_String_info 8 字符串类型字面量
    CONSTANT_Fieldref_info 9 字段的符号引用
    CONSTANT_Methidref_info 10 类中方法的符号引用
    CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
    CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
    CONSTANT_MethodHandle_info 15 表示方法句柄
    CONSTANT_MethodType_info 16 标识方法类型
    CONSTANT_InvokeDynamic_info 18 标识一个动态方法调用点

    之所以说常量池是最烦琐的数据,是因为这 14 中常量类型各自均有自己的结构。
    我们再来看图中常量池的第一项常量,它的标志位(偏移地hi:0x0000000A)是 0x0A,转换为十进制的值为 10,查常量表中对应的标志为 10 的常量属于 CONSTANT_Methodref_info 类型。
    在这里插入图片描述
    我们看一下 CONSTANT_Methodref_info 类型常量的结构:

    名称 类型 描述
    tag u1 值为10
    index u2 指向声明方法的类描述符 CONSTANT_Class_info 的索引项
    index u2 指向名称及类型描述符 CONSTANT_NameAndType 的索引项

    上图中的第一个 index 十六进制为 0x0004,即十进制的 4,表示指向常量池中第 4 个常量。
    第二个 index 十六进制为 0x000F,即十进制的 15,表示指向常量吃中的第 15 个常量。
    (先不管第4、15 常量表示什么)
    上面分析的是第一个常量值,接着分析第二个常量值,它的标志位(地址:0x0000000F)是 0x09,即十进制的 9,表示这个常量属于 CONSTANT_Fieldref_info 类型,此常量代表字段的符号引用。
    在这里插入图片描述
    CONSTANT_Fieldref_info 型常量的结构:

    名称 类型 描述
    tag u1 值为9
    index u2 指向声明字段的类或者接口描述符 CONSTANT_Class_info 的索引项
    index u2 指向字段描述符 CONSTANT_NameAndType 的索引项

    以上分析了 TestClazz.class 常量池中 18 个常量中的前两个,其余的 16 个依次类推:
    在这里插入图片描述
    需要注意的是第 18 个常量,tag 标志为 0x01 表示 CONSTANT_Utf8_info :

    名称 类型 描述
    tag u1 数量 1
    length u2 长度,表示占用几个字节
    bytes u1 占用 length 个字节

    注意 bytes 字段的长度,是根据 length 计算的,length 为 0x0010 转换十进制为 16,所以后面的 bytes 占用 16 个字节。
    最后将 14 中常量项的结构定义总结为下表,供大家参考:
    在这里插入图片描述

    1.4、访问标志

    在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等。具体标志位以及标志的含义见下表:

    标志名称 标志值 含义
    ACC_PUBLIC 0x0001 是否为 public 类型
    ACC_FINAL 0x0010 是否被声明为 final,只有类可设置
    ACC_SUPER 0x0020 是否允许使用 invokespecial 字节码指令的新语意,invokespecial指令的语意在 JDK 1.0.2 发生过改变,为了区别这条指令使用哪种语意,JDK 1.0.2 之后编译出来的类的这个标志都必须为真
    ACC_INTERFACE 0x0200 标识这是一个接口
    ACC_ABSTRACT 0x0400 是否为 abstract 类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
    ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生的
    ACC_ANNOTATION 0x2000 标识这是一个注解
    ACC_ENUM 0x4000 标识这是一个枚举

    在这里插入图片描述
    TestClazz.class 是一个普通的 java 类,不是接口、枚举,因此它的ACC_PUBLIC、ACC_SUPER标志为真,其他标志为假,因此它的 access_flags 的值为:0x0001|0x0020 = 0x0021。

    1.5、类索引、父类索引与接口索引集合

    类索引(this_class)和父类索引(super_class)都是一个 u2 类型的数据,而接口索引结合(interfaces)是一组 u2 类型的数据的集合,Class 文件中由这三项数据来确定这个类的继承关系。
    类索引和父类索引都是指向一个类型为 CONSTANT_Class_info 的类描述符常量。
    在这里插入图片描述
    图中看到,TestClazz中的类索引指向的是第 3 个常量,父类索引指向的是第 4 个常量。
    对于接口索引集合,入口的第一项——u2 类型的数据为接口计数器(interfaces_count),表示索引表的容量。如果该类没有实现任何接口,则该计数器值为 0 ,后面接口的索引表不再占用任何字节。

    1.6、字段表集合

    字段表用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
    字段表结构:

    类型 名称 数量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    字段修饰符放在 access_flags 项目中,它与类中的 access_flags 项目是非常类似的,都是一个 u2 的数据类型,其中可以设置的标志位和含义见表:

    标志名称 标志值 含义
    ACC_PUBLIC 0x0001 字段是否为 public
    ACC_PRIVATE 0x0002 字段是否为 private
    ACC_PROTECTED 0x0004 字段是否为 proctected
    ACC_STATIC 0x0008 字段是否为 static
    ACC_FINAL 0x0010 字段是否为 final
    ACC_VOLATILE 0x0040 字段是否为 volatile
    ACC_TRANSIENT 0x0080 字段是否为 transient
    ACC_SYNTHETIC 0x1000 字段是否由编译器自动产生的
    ACC_ENUM 0x4000 字段是否为 enum

    跟随 access_flags 标志的是两项索引值:name_index 和 descriptor_index。它们都是对常量池的引用,分别代表着字段的简单名称以及字段和方法的描述符。
    在这里插入图片描述

    1.7、方法表集合

    方法表的内容和字段表几乎采用了完全一致的方式,方法表结构:

    类型 名称 数量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    access_flags 方法访问标志:

    标志名称 标志值 含义
    ACC_PUBLIC 0x0001 方法是否为 public
    ACC_PRIVATE 0x0002 方法是否为 private
    ACC_PROTECTED 0x0004 方法是否为 proctected
    ACC_STATIC 0x0008 方法是否为 static
    ACC_FINAL 0x0010 方法是否为 final
    ACC_SYNCHRONIZED 0x0020 方法是否为 synchronized
    ACC_BRIDGE 0x0040 方法是否是由编译器产生的桥接方法
    ACC_VARARGS 0x0080 方法是否接受不定参数
    ACC_NATIVE 0x0100 方法是否为 native
    ACC_ABSTRACT 0x0400 方法是否为 abstract
    ACC_STRICTFP 0x0800 方法是否为 strictfp
    ACC_SYNTHETIC 0x1000 方法是否由编译器自动产生的

    TestClazz对应的位置:
    在这里插入图片描述
    注意,方法表集合只存放了方法名称,索引等,方法里的代码存放在方法属性表集合中一个名为“Code”的属性里面,这就是下面需要将到的属性表集合。

    1.8、属性表集合

    属性表(attribute_info)在 Class 文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
    与 Class 文件中其他的数据项目要求严格的顺序、长度和内容不同,属性表集合的限制稍微宽松些,不再要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java 虚拟机运行时会忽略掉它不认识的属性。
    虚拟机规范预定义的属性:

    属性名称 使用位置 含义
    Code 方法表 Java 代码编译成的字节码指令
    ConstantValue 字段表 final 关键字定义的常量值
    Deprecated 类、方法表、字段表 被声明为 deprecated 的方法和字段
    Exceptions 方法表 方法抛出的异常
    EnclosingMethod 类文件 仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
    InnerClasses 类文件 内部类列表
    LineNumberTable Code 属性 Java 源码的行号与字节码指令的对应关系
    LocalVariableTable Code 属性 方法的局部变量描述
    StackMapTable Code 属性 JDK 1.6 中新增的属性,供新的类型检查验证器(Tyoe Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
    Signature 类、方法表、字段表 这个属性用于支持泛型情况下的方法签名,在 Java 语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则 Signature 属性会为它记录泛型签名信息。由于 Java 的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息
    SourceFile 类文件 记录源文件名称
    SourceDebugExtension 类文件 JDK 1.6 中新增的属性,SourceDebugExtension 属性用于存储额外的调试信息。譬如在进行 JSP 文件调试时,无法通过 Java 堆栈来定位到 JSP 文件的行号,JSR-45 规范为这些非 Java 语言编写,却需要编异常字节码并运行在 Java 虚拟机中的程序提供了一个进行调试的标准机制,使用 SourceDebugExtension 属性就可以用于存储这个标准所新加入的调试信息
    Synthetic 类、方法表、字段表 标识方法或字段为编译器自动生成的
    LocalVariableTypeTable 它使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
    RuntimeVisibleAnnotations 类、方法表、字段表 为动态注解提供支持。RuntimeVisibleAnnotations 属性用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的
    RuntimeInvisibleAnnotations 类、方法表、字段表 与 RuntimeVisibaleAnnotations 属性作用刚好相反,用于指明哪些注解是运行时不可见的
    RuntimeVisibleParameterAnnotations 方法表 作用与 RuntimeVisibleAnnotations 属性类似,只不过作用对象为方法参数
    RuntimeInvisibleParameterAnnotations 方法表 作用与 RuntimeInvisibleAnnotations 属性类似,只不过作用对象为方法参数
    AnnotationDefault 方法表 用于记录注解类元素的默认值
    BootstrapMethods 类文件 JDK 1.7 中新增的属性,用于保存 invokedynamic 指令引用的引导方法限定符

    对于每个属性,它的名称需要从常量池中引用一个 CONSTANT_Utf8_info 类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个 u4 的长度属性去说明属性值所占用的位数即可,一个符合规则的属性表应该满足下表所定义的结构。
    属性表结构:

    类型 名称 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u1 info attribute_length

    1.8.1、Code 属性

    Java 程序方法体中的代码经过 Javac 编译器处理后,最终变为字节码指令存储在 Code 属性内。
    Code 属性表结构:

    类型 名称 数量 描述
    u2 attibute_name_index 1 属性名称,指向 CONSTANT_Utf8_info 型常量的索引,常量值固定为“Code”
    u4 attribute_length 1 属性值长度,由于属性名称索引与属性长度一共为 6 字节,所以属性值的长度固定为整个属性表长度减去 6 个字节
    u2 max_stack 1 操作数栈深度最大值,在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度
    u2 max_locals 1 局部变量表所需的存储空间,max_locals 的单位是 Slot,Slot 是虚拟机为局部变量分配内存所使用的最小单位
    u4 code_length 1 字节码长度
    u1 code code_length 字节码指令字节流,用于存储字节码指令的一系列字节流。文章的末尾会给出“虚拟机字节码指令表”。
    u2 exception_table_length 1
    exception_info exception_table exception_table_length
    u2 attributes_count 1
    attribute_info attributes attributes_count

    Code 属性是 Class 文件中最重要的一个属性,如果把一个 Java 程序中的信息分为代码(java代码)和元数据(类、字段、方法定义及其他信息)两部分,那么在整个 Class 文件中,Code 属性用于描述代码,所有其他数据项目都用于描述元数据。
    继续以 TestClazz.class 文件为例
    在这里插入图片描述
    它的操作数栈的最大深度和本地变量表的容量都为 0x0001,字节码区域所占空间的长度为 0x0005。
    虚拟机读取到字节码长度后,按照顺序依次读入紧随的 5 个字节,并根据字节码指令表翻译出所对应的字节码指令。
    翻译 “2A B7 00 0A B1” 的过程:
    1.读入 2A,查表得 0x2A 对应得指令为 aload_0,这个指令得含义是将第 0 个 Slot 中为 reference 类型得本地变量推送到操作数栈顶。
    2.读入 B7,查表得 0xB7 对应得指令为 invokespecial,这条指令的作用是以栈顶的 reference 类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private 方法或者它的父类的方法。
    3.读入 00 0A,这是 invokespecial 的参数,查常量池得 0x000A 对应的常量为实例构造器“”方法的符号引用。
    4.读入 B1,查表得 0xB1 对应得指令为 return,含义是返回此方法,这条指令执行后,当前方法结束。
    属性表集合除了 Code 属性,还有 Exceptions 属性、LineNumberTable 属性等等,这里就不一一介绍了。有兴趣得童鞋可以自行了解。

    2、javap 工具分析 Class

    在 JDK 的 bin 目录中,Oracle 公司已经为我们准备好一个专门用于分析 Class 文件字节码的工具:javap
    使用命令:

    javap -verbose TestClazz.class
    

    代码清单:

     Last modified 2019-1-14; size 285 bytes
      MD5 checksum c434da45f0fff84f21348a725448f2f5
      Compiled from "TestClazz.java"
    public class org.clazz.TestClazz
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #4.#15         // java/lang/Object."<init>":()V
       #2 = Fieldref           #3.#16         // org/clazz/TestClazz.m:I
       #3 = Class              #17            // org/clazz/TestClazz
       #4 = Class              #18            // java/lang/Object
       #5 = Utf8               m
       #6 = Utf8               I
       #7 = Utf8               <init>
       #8 = Utf8               ()V
       #9 = Utf8               Code
      #10 = Utf8               LineNumberTable
      #11 = Utf8               inc
      #12 = Utf8               ()I
      #13 = Utf8               SourceFile
      #14 = Utf8               TestClazz.java
      #15 = NameAndType        #7:#8          // "<init>":()V
      #16 = NameAndType        #5:#6          // m:I
      #17 = Utf8               org/clazz/TestClazz
      #18 = Utf8               java/lang/Object
    {
      public org.clazz.TestClazz();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 3: 0
    
      public int inc();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field m:I
             4: iconst_1
             5: iadd
             6: ireturn
          LineNumberTable:
            line 7: 0
    }
    SourceFile: "TestClazz.java"
    

    结束

    到此,相信大家能对字节码有一个较深的认识,Java 语言中的各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比 Java 语言本身更强大。

    附录

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 字节码文件及字节码指令

    千次阅读 2019-05-21 11:24:14
    我记得开始学习Java的第一堂课时,我的大学老师是这样说的,Java号称是“一次编写,到处运行”,为什么有底气这样说,是因为Java程序并不是直接运行在操作系统上的,它通过不同操作系统上的Java虚拟机实现了“到处...

    我记得开始学习Java的第一堂课时,我的大学老师是这样说的,Java号称是“一次编写,到处运行”,为什么有底气这样说,是因为Java程序并不是直接运行在操作系统上的,它通过不同操作系统上的Java虚拟机实现了“到处运行”的美好愿景。而且我的老师当时还说过,不止Java程序可以在Java虚拟机上运行,其他的程序也同样可以在Java虚拟机上运行。Java虚拟机并不认识具体的某种编程语言,而是编程语言要通过编译器编译成虚拟机认识的格式内容。那么虚拟机认识的格式内容就是本文主要讲述的一个神奇的东东–字节码文件。

    本文主要内容是字节码文件的格式及字节码指令。

    一、字节码文件

    字节码是什么?

    一开始我以为字节码是个非常复杂的东西,不修炼几千年根本无法了解其中的奥秘。但是剥丝抽茧后,我发现其实本质上就是一个特定格式排列各个字段信息的二进制流文件,用于虚拟机识别执行。根据Java虚拟机规范,字节码文件使用类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型,无符号数和表。

    下面首先来看一段为字节码而造的代码:

    package com.earl.se.basics;
    
    public class Demo5 implements Runnable {
    
        private int number = 1;
    
        @Override
        public void run() {
            System.out.println("Demo5 thread say hello byte code.");
        }
    
        public static void main(String[] args){
    
            System.out.println("Main thread say hello byte code.");
    
            new Thread(new Runnable() {
                @Override
                public void run() {
    
                }
            }).start();
        }
    }
    
    

    当我通过javac命令编译了Demo5.java后,会生成字节码文件Demo5.class,通过十六进制编辑器WinHex将其打开,可以看到是如下的格式:

    在这里插入图片描述

    哈哈,仅仅是这么几行代码,生成的字节码文件看起来是不是都很头大,毕竟人脑还是适合阅读人类的文字而非机器所喜欢的文字。但是没办法,既然选择要正面刚虚拟机的知识,那么了解其内部的结构是必不可少的环节。接下来先介绍一下字节码文件中各个部分分别代表的意义,概念扫盲之后,其实是可以通过咱们开发常用的IDE来进行查看字节码文件的,这样是不是就显得轻松多了,嘿嘿~

    字节码文件结构

    字节码文件有两种数据格式:

    • 无符号数属于基本数据类型,以u1,u2,u4,u8分别表示1个字节,2个字节,4个字节,8个字节的无符号数。
    • 表是由多个无符号数或其他表作为数据项构成的复合数据类型。所有的表都是以“_info”结尾。

    下表是按照字节码文件中各字段排列顺序的全部格式:

    名称 类型 数量
    魔数(magic) u4 1
    子版本号(minor_version) u2 1
    主版本号(major_version) u2 1
    常量池计数值(constant_pool_count) u2 1
    常量池(constant_pool) cp_info constant_pool_count-1
    访问标志(access_flag) u2 1
    类索引(this_class) u2 1
    父类索引(super_class) u2 1
    接口计数值(interfaces_count) u2 1
    接口索引集合(interfaces) u2 interface_count
    字段表集合计数器(fields_count) u2 1
    字段表集合(fields) field_info fields_count
    方法表集合计数器(methods_count) u2 1
    方法表集合(methods) method_info methods_count
    属性表集合计数器(attributes_count) u2 1
    属性表集合(attributes) attribute_info attributes_count

    下面依次来说明各个类型是什么含义:

    每个字节码文件开头的4个字节称为魔数,作用是确定这个字节码文件是否是一个能被虚拟机接受的字节码文件。在上图字节码文件中看到的开始的四个字节0xCAFEBABE代表的就是Java。

    接着魔数后的第5个和第6个两个字节表示子版本号,接下来的第7个和第8个字节表示主版本号。

    接下来是常量池相关信息。由于常量池中常量的数量是不固定的,因此在常量池入口处设置了一个常量池计数值。常量池中主要存放的是字面量和符号引用。
    字面量即如java语言中的常量概念。
    符号引用则属于编译原理方面的概念,包括类和接口的全限定名,字段的名称和描述符,方法的名称和描述符。
    在字节码文件中并不存储各个方法,字段的最终内存布局信息,因此当虚拟机运行时,需要从常量池中获取到符号引用,在类创建或运行时在找到具体的内存地址。

    接下来的两个字节代表访问标志,用于识别类或接口层次的访问信息,包括这个字节码文件对应的是类还是接口,是否定义为public类型,是否定义为abstract类型,类的话是否是final声明等。

    接下来是类索引,父类索引和接口索引集合。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类全限定名,接口索引集合描述这个类实现了哪些接口(如果这个类是接口,那么就是继承了哪些接口)。接口计数值表示这个类或接口实现或继承的接口数量,如果没有实现或继承接口,这个值为0。

    字段表集合计数器与字段表集合用来描述接口或类中声明的变量。

    方法表集合计数器与方法表集合用来描述接口或类中声明的方法。

    属性表集合计数器与属性表集合用来描述Class文件等在某些场景的专有信息。

    通过IDE来查看字节码文件

    扫盲结束,接下来讲讲如何通过我们常用的IDE来查看字节码文件。这里我选用的是IDEA,首先要安装字节码查看的插件。File->Settings->Plugins,在Marketplace中搜索jclasslib,然后安装jclasslib Bytecode viewer,重启IDEA即可完成安装。

    接下来就可以使用jclasslib来查看字节码文件了,还是以Demo5.java为例,在IDEA中打开这个文件,然后点击View->Show Bytecode With jclasslib,这样就会看到如下图的展示:

    在这里插入图片描述

    这样看起来是不是就清楚很多了,右侧的窗口中将字节码文件中的各个字段都直观地展示出来,再无需我们手动去将十六进制转换为人类文字了。

    二、字节码指令

    字节码指令是一个字节长度的,代表着某种特定操作含义的数字操作码,由操作码及代表此操作所需操作参数构成。主要包含以下的一些指令操作:

    1. 加载和存储指令:用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,主要包含以下的操作:
      • 将局部变量加载到操作栈,涉及指令有load指令
      • 将一个数值从操作数栈存储到局部变量表,涉及指令有store指令
      • 将一个常量加载到操作数栈,涉及指令有push指令,const指令
      • 扩充局部变量表的访问索引指令,wide指令
    2. 运算指令:用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入操作栈顶。运算主要分为两种,对整型数据进行运算和对浮点型数据进行运算,主要包含以下操作:
      • 加法指令:add
      • 减法指令:sub
      • 乘法指令:mul
      • 除法指令:div
      • 求余指令:rem
      • 取反指令:neg
      • 位移指令:shl,shr,ushr
      • 按位或指令:or
      • 按位与指令:and
      • 按位异或指令:xor
      • 局部变量自增指令:inc
      • 比较指令:cmpg,cmpl,cmp
    3. 类型转换指令:用于将两种不同的数值类型进行相互转换,这些转换操作主要用于代码中的显式类型转换。
      • 对于小类型转大类型的操作,虚拟机直接支持,无需转换指令。
      • 对于大类型转小类型的操作,涉及指令有i2b,i2c,i2s,l2i,f2i,f2l,d2i,d2l,d2f。大转小的操作可能会导致精度丢失。
    4. 对象创建与访问指令,主要涉及以下操作:
      • 创建类实例的指令:new
      • 创建数组的指令:newarray,anewarray,multianewarray
      • 访问类字段和实例字段指令:getfield,getstatic,putfield,putstatic
      • 加载数组元素到操作数栈指令:aload
      • 将操作数栈的值存储到数组元素指令:astore
      • 取数组长度:arraylength
      • 检查类实例类型指令:instanceof,checkcast
    展开全文
  • 字节码实践 -- 使用 ASM 实现 AOP

    千次阅读 2018-10-21 22:25:10
     ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 ....

     

         ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

         可以负责任的告诉大家,ASM只不过是通过 “Visitor” 模式将 “.class” 类文件的内容从头到尾扫描一遍。因此如果你抱着任何更苛刻的要求最后都将失望而归。我们常见的 Aop 框架几乎都属于 ASM 框架的泛生品。

        众所周知,Aop 无论概念有多么深奥。它无非就是一个“Proxy模式”。被代理的方法在调用前后作为代理程序可以做一些预先和后续的操作。这一点想必读者都能达到一个共识。因此要想实现 Aop 的关键是,如何将我们的代码安插到被调用方法的相应位置。

    好了,开始正题,先写个最简单的"Hello xifeijian",代码如下:

    package com.example.demo.controller.aop;
    
    /**
     * Created by uc on 2018/10/18.
     */
    public class TestBean {
        public void halloAop(){
            System.out.println("Hello xifeijian");
        }
    }

    接下来我想在halloAop中实现AOP,在调用该方法之前分别调用该类的before和after,怎么实现?(具体功能不限,此处仅是demo)

    package com.example.demo.controller.aop;
    
    /**
     * Created by uc on 2018/10/18.
     */
    public class AopInterceptor {
        public static void beforeInvoke() {
            System.out.println("before");
        };
        public static void afterInvoke() {
            System.out.println("after");
        };
    }

    下面开始安插 Aop 实现的 ASM 代码:

    package com.example.demo.controller.aop;
    
    import jdk.internal.org.objectweb.asm.ClassReader;
    import jdk.internal.org.objectweb.asm.ClassWriter;
    import jdk.internal.org.objectweb.asm.Opcodes;
    
    import java.io.InputStream;
    
    /**
     * Created by uc on 2018/10/18.
     */
     public class AopClassLoader extends ClassLoader implements Opcodes {
        public AopClassLoader(ClassLoader parent) {
            super(parent);
        }
        public  Class<?> loadClass(String name) throws ClassNotFoundException {
            if (!name.contains("TestBean_Tmp"))
                return super.loadClass(name);
            try {
                ClassWriter cw = new ClassWriter(0);
                //
                InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/example/demo/controller/aop/TestBean.class");
                ClassReader reader = new ClassReader(is);
                reader.accept(new AopClassAdapter(ASM4, cw), ClassReader.SKIP_DEBUG);
                //
                byte[] code = cw.toByteArray();
                //            FileOutputStream fos = new FileOutputStream("c:\\TestBean_Tmp.class");
                //            fos.write(code);
                //            fos.flush();
                //            fos.close();
                return this.defineClass(name, code, 0, code.length);
            } catch (Throwable e) {
                e.printStackTrace();
                throw new ClassNotFoundException();
            }
        }
    }
    

     接下来我们实现一个ClassAdapter,代码如下:

    package com.example.demo.controller.aop;
    
    import jdk.internal.org.objectweb.asm.ClassVisitor;
    import jdk.internal.org.objectweb.asm.MethodVisitor;
    import jdk.internal.org.objectweb.asm.Opcodes;
    
    /**
     * Created by uc on 2018/10/18.
     */
    public class AopClassAdapter extends ClassVisitor implements Opcodes {
        public AopClassAdapter(int api, ClassVisitor cv) {
            super(api, cv);
        }
    
    
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            //更改类名,并使新类继承原有的类。
            super.visit(version, access, name + "_Tmp", signature, name, interfaces);
            {
                MethodVisitor mv = super.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
                mv.visitCode();
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, name, "<init>", "()V");
                mv.visitInsn(RETURN);
                mv.visitMaxs(1, 1);
                mv.visitEnd();
            }
        }
    
    
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            if ("<init>".equals(name))
                return null;
            if (!name.equals("halloAop"))
                return null;
            //
            MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
            return new AopMethod(this.api, mv);
        }
    }
    
    

    最后就是使用 ASM 改写 Java 类:

        package com.example.demo.controller.aop;
        
        import javassist.bytecode.Opcode;
        import jdk.internal.org.objectweb.asm.MethodVisitor;
        import jdk.internal.org.objectweb.asm.Opcodes;
        
        /**
         * Created by uc on 2018/10/18.
         */
        
        
        public class AopMethod extends MethodVisitor implements Opcodes{
            public AopMethod(int api, MethodVisitor mv){
                super(api ,mv);
            }
        
            public void visitCode(){
                super.visitCode();
                this.visitMethodInsn(INVOKESTATIC, "com/example/demo/controller/aop/AopInterceptor", "beforeInvoke", "()V");
            }
            public void visitInsn(int opcode){
                if(opcode==RETURN){
                    mv.visitMethodInsn(INVOKESTATIC, "com/example/demo/controller/aop/AopInterceptor", "afterInvoke", "()V");
                }
                super.visitInsn(opcode);
            }
        
        }
        
    

     准备工作已经完成,最后我们只需要编写一个 ClassLoader 加载我们的新类就可以了,新类的名称后面多了“_Tmp”。

    package com.example.demo.controller.aop;
    
    import com.example.demo.DemoApplication;
    import com.example.demo.aop.LogClassVisitor;
    //import com.example.demo.aop.MyClassLoader;
    import jdk.internal.org.objectweb.asm.ClassReader;
    import jdk.internal.org.objectweb.asm.ClassWriter;
    import org.springframework.boot.SpringApplication;
    
    /**
     * Created by uc on 2018/10/21.
     */
    public class AsmAop {
        public static void main(String[] args)throws Exception
        {
            Class<?> clazz = new AopClassLoader(Thread.currentThread().getContextClassLoader()).loadClass("com.example.demo.controller.aop.TestBean_Tmp");
            clazz.getMethods()[0].invoke(clazz.newInstance());
        }
    }
    

    运行AsmAop类的main函数,得到以下结果,完成我们预期的AOP效果:

    以上是该实践的所有源码,轻轻松松Demo一下~

    展开全文
  • Android AOP实现原理之字节码插桩(一)参考博客 Android AOP之字节码插桩博客 Android热补丁动态修复技术(三)—— 使用Javassist注入字节码,完成热补丁框架雏形(可使用)由衷感谢以上博主分享的技术知识!...
  • 文章目录基本概念什么是字节码Javap命令查看字节码文件字节码文件解读static、final、volatile在字节码中的体现 基本概念 static:static修饰的变量被所有类实例共享,静态变量在其所在类被加载时进行初始化 final:...
  • 文章目录前言从AOP说起静态代理动态代理JavaProxyCGLIB字节码增强实现AOPASMJavaAssist运行时类加载Instrumentation接口JavaAgentPremainClass随JVM进程启动AgentClass以Attach方法注入Agent总结 前言 在上篇文章...
  • 字节码详解

    千次阅读 2020-10-08 18:16:21
    Java作为一款“一次编译,到处运行”的编程语言,跨平台靠的是JVM实现对不同操作系统API的支持,而一次编译指的就是class字节码;即我们编写好的.java文件,通过编译器编译成.class文件,JVM负责加载解释字节码文件...
  • 关于Java字节码

    千次阅读 2018-09-24 10:25:23
    关于Java字节码 概述 从写Java文件到编译成字节码文件(也就是.class文件)的过程也就是Java文件编译的过程,我们所写的是Java文件而Java虚拟机编译的是字节码文件 class文件格式 ...
  • 注意:javap 查看【class文件的字节码】信息 Java字节码深入解析 一:Java字节代码的组织形式  类文件{  OxCAFEBABE,小...
  • Java字节码增强探秘

    千次阅读 2019-09-10 14:03:58
    1.字节码 1.1 什么是字节码? Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也...
  • 字节码简介

    千次阅读 2018-12-31 16:02:34
    什么是字节码? Java的使命就是一次编写,到处执行。在不同的操作系统,不同硬件平台上,均可以不用修改代码即可顺畅地执行。Java是如何实现跨平台的?答案是增加一个中间层,即字节码(Bytecode)。 Java所有的...
  • dalvik字节码问答

    千次阅读 2016-05-10 19:52:18
    什么是字节码字节码的长度怎么计算? 字节码的格式是什么? const类指令有那些? 和类、对象操作相关的指令有哪些? switch相关指令是怎么回事? Array数据操作指令有那些? quick类型的指令有那些,起到什么作用...
  • 深入理解JVM-字节码

    千次阅读 2020-03-07 21:11:00
    Java字节码结构 Access_Flag访问标志 Fileds 字段表 Methods 方法表: 方法的属性结构 Code结构 其他结构 附加属性表 字节码补充注意事项 栈帧 字节码解释执行 Java字节码结构 Class字节码中有两...
  • Spring(九)CGLIB字节码增强

    千次阅读 2017-01-08 16:36:57
    采用字节码增强框架cglib,在运行时 创建目标的子类 ,从而对目标类进行增强。 下面通过一个案例来说明CGLIB动态代理 目标类 总共实现三个业务 添加用户信息(addUser) 更新用户信息(update
  • 从一个class文件深入理解Java字节码结构

    万次阅读 多人点赞 2018-05-15 10:01:56
    我们都知道,Java程序最终是转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容呢,这篇文章带你深入理解Java字节码中的结构。 1.Demo源码 首先,...
  • Java字节码介绍及动态修改类

    千次阅读 2018-09-06 18:36:30
    前言 对于Java字节码,它是在Java类的编译过程产生的,即由.java源文件到.class二进制字节码文件的过程。...修改字节码可以通过ASM这个开源框架实现,ASM是一个Java字节码引擎的库,具体可以查看...
  • 深入JVM字节码执行引擎

    万次阅读 多人点赞 2015-10-30 18:57:11
    我们都知道,在当前的Java中(1.0)之后,编译器讲源代码转成字节码,那么字节码如何被执行的呢?这就涉及到了JVM的字节码执行引擎,执行引擎负责具体的代码调用及执行过程。
  • JDK实现动态代理: ...利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 动态代理分为两种: * jdk的动态代理 * 代理对象和目标对象实现了共同的接口 * 拦截器必须实现I...
  • 深入理解 Java 虚拟机(一)~ class 字节码文件剖析

    千次阅读 多人点赞 2019-09-30 16:29:57
    本文分析了字节码文件的组成,如魔数、字节码版本、常量池、字段、方法、属性等,还介绍了 invokeDynamic 指令,并分析了其实现原理;接着分析了字节码指令集, 并通过一个案例分析了其对应的指令,每执行完一个指令...
  •  ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .
  • Java字节码指令集

    千次阅读 2014-01-29 22:11:59
    字节码指令集  Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(Opcode)以及跟随其后的零至多个代表此操作所需参数的操作数(Operands)所构成。  对于大部分为与数据类型相关的字节码...
  • class字节码文件

    千次阅读 2018-05-18 12:20:42
    写在前面的话 class文件结构可以说是一套规范,不一定是Java...由于这种字节码可以运行在jvm上实现跨平台的亮点。所以不只是Java语言编译器可以编译成这种文件。class文件是学习jvm中比较重要的一环。我尽量用...
  • JAVA字节码增强技术之ASM示例

    千次阅读 2017-04-11 16:56:32
    一、什么是ASM ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前...
  • 我们把C代码编译成java字节码,这样我们的C语言便具备了可跨品台属性。通过把C语言编译成java字节码,我们不但能够继续学习和掌握编译原理相关的算法技术,于此同时,还能深入理解java虚拟机的基本原理,此乃...
  • Java字节码操纵框架ASM小试

    千次阅读 2015-05-29 17:06:11
    Java字节码操纵框架ASM小试 转自:http://www.oseye.net/user/kevin/blog/304 本文主要内容: ASM是什么 JVM指令 Java字节码文件 ASM编程模型 ASM示例 参考资料汇总 JVM详细指令 ASM是什么 ...
  • 参考: Android字节码插桩采坑笔记 通过自定义 Gradle 插件修改编译后的 class 文件 ASM官方文档
  • 可以看下Iterable接口的注释,它说明了除了数组外,其他类想要使用for-each循环必须实现这个接口。这一点表明除了数组外的for-each可能底层是由迭代器实现的。 Iterable接口在1.8之前只有一个方法,Iterator iterator...
  • 虚拟机字节码执行引擎

    千次阅读 2017-09-20 09:25:53
    Java虚拟机字节码执行引擎是Java虚拟机最核心的组成部分之一。它负责Java程序的执行,执行的过程主要包括方法的调用和方法代码的执行(对代码块代码的执行其实在虚拟机内部也是组装成方法执行的)。 所以对执行引擎...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 143,686
精华内容 57,474
关键字:

继承的字节码实现