精华内容
下载资源
问答
  • Java字节码

    千次阅读 2019-01-14 13:23:40
    使用字节码可以玩出很多高级的玩法,最高级的还是在 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 语言本身更强大。

    附录

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

    展开全文
  • java字节码技术

    2020-08-08 15:01:20
    什么是java字节码技术 Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以...

    什么是java字节码技术
        Java之所以可以“一次编译,到处运行”,一是因为JVM针对各种操作系统、平台都进行了定制,二是因为无论在什么平台,都可以编译生成固定格式的字节码(.class文件)供JVM使用。因此,也可以看出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用javac命令编译源代码为字节码文件,一个.java文件从编译到运行的示例如图1所示。

    字节码技术应用场景
    AOP技术
    Lombok去除重复代码插件
    动态修改class文件等

    字节技术优势
    Java字节码增强指的是在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。Java字节码增强主要是为了减少冗余代码,提高性能等。

    实现字节码增强的主要步骤为:
    修改字节码,在内存中获取到原来的字节码,然后通过一些工具(如 ASM,Javaasist)来修改它的byte[]数组,得到一个新的byte数组。
    使修改后的字节码生效,有两种方法:
              方法一: 自定义ClassLoader来加载修改后的字节码;

              方法二:替换掉原来的字节码:在JVM加载用户的Class时,拦截,返回修改后的字节码;或者在运行时,使用Instrumentation.redefineClasses方法来替换掉原来的字节码

    java字节码类库

    Javassist 

    Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京技术学院的数学和计算机科学系的 Shigeru Chiba 所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。

    BCEL 

    Byte Code Engineering Library (BCEL),这是Apache Software Foundation 的Jakarta 项目的一部分。BCEL是 Java classworking 最广泛使用的一种框架,它可以让您深入 JVM 汇编语言进行类操作的细节。BCEL与Javassist 有不同的处理字节码方法,BCEL在实际的JVM 指令层次上进行操作(BCEL拥有丰富的JVM 指令级支持)而Javassist 所强调的源代码级别的工作。

    ObjectWeb ASM

    ObjectWeb ASM轻量级的Java字节码处理框架。它可以动态生成二进制格式的stub类或其他代理类,或者在类被JAVA虚拟机装入内存之前,动态修改类。ASM 提供了与 BCEL和SERP相似的功能,只有22K的大小,比起350K的BCEL和150K的SERP来说,是相当小巧的,并且它有更高的执行效率,是BCEL的7倍,SERP的11倍以上。

    cglib 

    cglib是一个强大的,高性能,高质量的Code生成类库。它可以在运行期扩展Java类与实现Java接口。Hibernate用它来实现PO字节码的动态生成。

    Cojen

    Cojen是一个动态Java字节码生成和分解工具。使用一个建造器来方便地生成代码,而不用再选择精确的指令。Cojen提供的工具类演示了代码生成器和加载器的使用。

    JclassLib

    JClassLib不但是一个字节码阅读器而且还包含一个类库允许开发者读取,修改,写入Java Class文件与字节码。

    SERP 

    SERP字节码框架提供一组高级的APIs进行字节码各主方面的操作。SERP字节码框架能够使你不需要明白class文件格式与JVM 指令就可以轻松完成bytecode 操作

    Trove Class File API 

    这个类文件API简化了Java class文件的创建。它允许你调用高级的方法来产生byte-code,不是直接编写byte-code。

    Package gnu.bytecode

    Package gnu.bytecode包含一些可以生成,读取,打印Java字节码文件的Java类。这个包是Kawa项目的一部分。

    Classfile Reader & Writer

    这个包使得读写java class文件变得很容易。它可以很容易得把class文件的内容显示给用户(除非你要统计Bug的输出数)与分解字节码。

    Jiapi 

    Jiapi是一个用来改变正常Class装载过程的工具。Jiapi通过操作类的字节码来代替Class原来被装载的过程。被操作的Class传给一个可以把Class装载到Java虚拟机的类装载器。通过Jiapi工具被编译的Class可进行预处理操作并且可延缓执行被修改的Class在文件系统上序列化操作。

    JBET

    通过JBET(Java Binary Enhancement Tool )的API可对Class文件进行分解,重新组合,或被编辑。JBET也可以创建新的Class文件。JBET用一种结构化的方式来展现Javabinary (.class)文件的内容,并且可以很容易的进行修改。

    Retroweaver

    Retroweaver是一个字节码组织器,它能够让你用java1.4的虚拟机来运行用1.5的新特性所编写Class.Retroweaver的操作是把用1.5编译器编译的Class文件转换成可以在任何1.4虚拟机上运行Class文件.

    Jen 

    Jen为字节码操作与生成提供一组高级API.利用它可以对Java classe进行重命名,把一个数据成员(Field)改成Public访问权限和新增一个构建函数等操作。Jen基于ASM2.1并完全支持Java5注释(annotations)与范型(Generics)。此外它还提供一组额外的工具类用于在运行期生成新的classes。

    Soot 

    Soot是一个Java优化框架.它提供了四种中间(representation)表现用于分析与转换Java字节码.Soot既可以作为优化和检查class文件的工具也可以作为一个开发与优化Java字节码的框架。在2.0以上的版本Soot还包含一个Eclipse插件。

    ReJava

    ReJava提供一个类库用于操作class文件(包括bytecode)和一个GUI工具用于编辑,混淆,注入代码,搜索class文件。

    jclazz 

    jclazz是一个Java字节码查看器与反编译工具。支持java1.4到1.6版本的字节码。

    展开全文
  • Retrotranslator的网站位于...通过该工具进行转换,可以完美的运行在java14环境下面。当 然,该工具也不是完全可以兼容jdk15的新功能,在其网站上面也谈及了不能够实现的部分,可以作为参考。
  • 注意:javap 查看【class文件的字节码】信息 Java字节码深入解析 一:Java字节代码的组织形式  类文件{  OxCAFEBABE,小...

    注意: javap 查看【class文件的字节码】信息                                                                                                                       

    Java字节码深入解析

    一:Java字节代码的组织形式

      类文件{

      OxCAFEBABE,小版本号,大版本号,常量池大小,常量池数组,访问控制标记,当前类信息,父类信息,实现的接口个数,实现的接口信息数组,域个数,域信息数组,方法个数,方法信息数组,属性个数,属性信息数组

      }

      二:查看方法 --- javap命令

      例子:有一个Java类Demo.java

    /**
     * @author honglei
     * @since 2019-08-11
     */
    public class Demo {
        /**
         * str1
         */
        private String str1;
        /**
         * str2
         */
        private String str2;
        /**
         * num1
         */
        private int num1;
        /**
         * num2
         */
        private int num2;
        /**
         * str3
         */
        public static final String STATIC_DATA = "hello world";
    
        /**
         * str1
         */
        private void sayHello1() {
            System.out.println("this is method1...");
        }
    
        /**
         * sayHello2
         */
        private void sayHello2() {
            System.out.println("this is method2...");
        }
    
        /**
         * sayHello3
         */
        private void sayHello3() {
            System.out.println("this is method3...");
        }
    }

      通过jdk自带的反编译工具命令 javap 可以查看class文件的字节码信息

    D:\>javap -verbose Demo >> Demo.txt

      Demo.txt:

    sai:springbootdemo ws$ javap -verbose Demo.class 
    Classfile /Users/ws/dev/SourceTree/springboot/springboot-demo/target/classes/com/example/springbootdemo/Demo.class
      Last modified Aug 11, 2019; size 1055 bytes
      MD5 checksum 98bea2d49bf0c5386ac21e3cae91d404
      Compiled from "Demo.java"
    public class com.example.springbootdemo.Demo
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #9.#36         // java/lang/Object."<init>":()V
       #2 = Fieldref           #37.#38        // java/lang/System.out:Ljava/io/PrintStream;
       #3 = String             #39            // this is method1...
       #4 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = String             #42            // this is method2...
       #6 = String             #43            // this is method3...
       #7 = String             #44            // test
       #8 = Class              #45            // com/example/springbootdemo/Demo
       #9 = Class              #46            // java/lang/Object
      #10 = Utf8               str1
      #11 = Utf8               Ljava/lang/String;
      #12 = Utf8               str2
      #13 = Utf8               num1
      #14 = Utf8               I
      #15 = Utf8               num2
      #16 = Utf8               STATIC_DATA
      #17 = Utf8               ConstantValue
      #18 = String             #47            // hello world
      #19 = Utf8               <init>
      #20 = Utf8               ()V
      #21 = Utf8               Code
      #22 = Utf8               LineNumberTable
      #23 = Utf8               LocalVariableTable
      #24 = Utf8               this
      #25 = Utf8               Lcom/example/springbootdemo/Demo;
      #26 = Utf8               sayHello1
      #27 = Utf8               sayHello2
      #28 = Utf8               sayHello3
      #29 = Utf8               main
      #30 = Utf8               ([Ljava/lang/String;)V
      #31 = Utf8               args
      #32 = Utf8               [Ljava/lang/String;
      #33 = Utf8               MethodParameters
      #34 = Utf8               SourceFile
      #35 = Utf8               Demo.java
      #36 = NameAndType        #19:#20        // "<init>":()V
      #37 = Class              #48            // java/lang/System
      #38 = NameAndType        #49:#50        // out:Ljava/io/PrintStream;
      #39 = Utf8               this is method1...
      #40 = Class              #51            // java/io/PrintStream
      #41 = NameAndType        #52:#53        // println:(Ljava/lang/String;)V
      #42 = Utf8               this is method2...
      #43 = Utf8               this is method3...
      #44 = Utf8               test
      #45 = Utf8               com/example/springbootdemo/Demo
      #46 = Utf8               java/lang/Object
      #47 = Utf8               hello world
      #48 = Utf8               java/lang/System
      #49 = Utf8               out
      #50 = Utf8               Ljava/io/PrintStream;
      #51 = Utf8               java/io/PrintStream
      #52 = Utf8               println
      #53 = Utf8               (Ljava/lang/String;)V
    {
      public static final java.lang.String STATIC_DATA;
        descriptor: Ljava/lang/String;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
        ConstantValue: String hello world
    
      public com.example.springbootdemo.Demo();
        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 7: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/example/springbootdemo/Demo;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=1, args_size=1
             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: ldc           #7                  // String test
             5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
             8: return
          LineNumberTable:
            line 54: 0
            line 55: 8
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  args   [Ljava/lang/String;
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "Demo.java"
    

    解析:

      1、版本号 major version: 49 //java版本 jdk1.6显示的是50, jdk1.5显示的是49,jdk1.4显示的是58 , 高版本能执行低版本的class文件

      2、常量池Constant pool

      Method:方法

      Field:字段

      String:字符串

      Asciz:签名如<init>由jvm调用,其他是不能够去调用它的

      NameAndType:变量名的类型

      Class:类

      通过字节码,我们可以看到Demo类 继承于java.lang.Object,如果类中没有显式声明构造函数的话,编译器会插入一个缺省无参的构造函数(构造函数在JVM级别是显示成<init>的普通函数)。

      三:检测代码的效率问题

      学习Java的过程中,都会了解到字符串合并时要用到StringBuffer 来代替String,那下面就来通过Java字节码来验证两种方式的效率性。

      例子:一个Java类 TestString.java

    • <strong>public class TestString { 
    •     public String testString(String str1, String str2){ 
    •        return str1 + str2; 
    •     } 
    •     public String testStringBuffer(StringBuffer sb, String str){ 
    •        return sb.append(str).toString(); 
    •     } 
    •  </strong>

      javap –c TestString 后字节码信息:

    • Compiled from "TestString.java" 
    • public class TestString extends java.lang.Object{ 
    • public TestString(); 
    •   Code: 
    •    0:      aload_0 
    •    1:      invokespecial  #8; //Method java/lang/Object."<init>":()V 
    •    4:      return 
    •   
    • public java.lang.String testString(java.lang.String, java.lang.String); 
    •   Code: 
    •    0:      new #16; //class java/lang/StringBuilder 
    •    3:      dup 
    •    4:      aload_1 
    •    5:      invokestatic    #18; //Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 
    •    8:      invokespecial  #24; //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 
    •    11:     aload_2 
    •    12:    invokevirtual  #27; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
    •    15:    invokevirtual  #31; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
    •    18:    areturn 
    •   
    • public java.lang.String testStringBuffer(java.lang.StringBuffer, java.lang.String); 
    •   Code: 
    •    0:      aload_1 
    •    1:      aload_2 
    •    2:      invokevirtual  #40; //Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 
    •    5:      invokevirtual  #45; //Method java/lang/StringBuffer.toString:()Ljava/lang/String; 
    •    8:      areturn 
    • }

      从上面编译后的字节码信息可以看出来,方法testString 调用了五个方法:new 、invokestatic 、invokespecial 和两个invokevirtual ; 而testStringBuffer 方法只调用了两个invokevirtual 方法。第一个方法比第二个方法多做了好多工作,其效率当然是要低的。而且我们从java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

      可以看出来其实对于String字符串合并,内部还是转化为StringBuilder的方法调用,这是因为String是长度不可变的,所以不如直接采用StringBuilder(与StringBuffer 长度都是可变的,只不过前者是非线程安全,后者是线程安全)进行字符串合并。

     

           

                                                                     使用Intellij idea如何快速查看Java类字节码
     

    1、打开File-Settings

     

    2:打开Tools-External Tools,右侧点击绿色“+”

     

    3:填写一些内容规则:Name是在类中,右键时使用时的名称

    4:代码处右键,即可找到添加的功能

     

    5、通过jdk自带的反编译工具命令 javap 可以查看class文件的字节码信息

    -verbose

    或者

    -c

    都可以,详情可以查看javap命令如何使用

     

     

    展开全文
  • 让编译器把C语言代码中对数组的相关操作编译成java字节码

    更详细的讲解和代码调试演示,请参看视频:
    更详细的讲解和代码调试演示过程,请参看视频
    用java开发C语言编译器

    jvm字节码对应于数组操作时有一套特定的指令,当字节码要在虚拟机上生成一个含有10个元素的整形数组时,它会执行以下指令:

    sipush 10
    newarray int
    astore 0

    第一条语句sipush 10 先将数组所包含的元素的个数压到堆栈上,生成数组时对应一条特定指令就是 newarray, 该指令后面跟着要生成的数组类型,如果想生成整形数组,那么newarray 指令后面就跟着int, 如果想生成浮点型数组,那么newarray 后面就得跟着float. 执行完这两条语句后堆栈顶部存储着含有10个元素的整形数组的对象实例:
    stack: int[10]

    接着的指令astore 0把堆栈上的含有10个元素的整形数组对象转移到局部变量队列:
    stack: null
    local_list: int[10]

    字节码要想读取数组中的某个元素的值时,例如要读取a[7]的值时,可以执行如下指令:

    aload 0
    sipush 7
    iaload

    aload 0指令把存储在局部变量队列的数组对象加载到堆栈,然后把要读取的数组元素的下标压入堆栈,由于我们要读取的数组元素的下标是7,因此我们通过指令sipush 7把数值7压入堆栈,最后指令iaload把a[7]的值压入到堆栈,由于我们刚生成的数组a[10]还没有初始化,所以a[7]的值是0,上面的指令执行后,虚拟机的情况如下:
    stack: 0, int[10]
    local_stack: int[10]

    指令iload前面的i与数组的类型要一致,如果数组类型是浮点型,对应的指令就应该是fload.

    如果要想对数组中的某个元素赋值,加入我们要把10赋值给数组下标为3的元素,也就是我们想实现a[3] = 10,那么我们可以执行如下指令:

    aload 0
    sipush 3
    sipush 10
    iastore 

    指令aload 0先把数组对象加载到堆栈,然后把要赋值的元素的下标压入堆栈,由于我们想对下标为3的元素赋值,所以通过指令sipush 3把数值3压到堆栈。接着把要赋值的内容压入堆栈,因为我们想给a[3]赋值10,所以通过sipush 10 把数值10 压入堆栈,最后执行指令iastore 把数值10存入数值下标为3的元素,iastore前面的i与数组的类型是相关的,如果数组类型是浮点值,那么对应的指令应该是fastore。

    有了上面的理论基础后,我们看看如何把下面的C语言代码编译成java字节码:

    void main() {
        int a[10];
        a[3] = 10;
        printf("value of a[3] is :%d", a[3]);
    }

    代码首先定义了一个含有10个元素的整形数组,当编译成java字节码时,我们的编译器首先需要使用相应指令在虚拟机中生成对应的数组对象,相应代码如下,在ProgramGenerator.java中:

    public void createArray(Symbol symbol) {
            if (arrayNameMap.containsKey(symbol.getScope())) {
                if (arrayNameMap.get(symbol.getScope()).equals(symbol.getName())) {
                    return;
                }
            }
    
            arrayNameMap.put(symbol.getScope(), symbol.getName());
    
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); 
            if (declarator == null) {
                return;
            }
    
            String type = "";
            if (symbol.hasType(Specifier.INT)) {
                type = "int";
            }
    
            int num = declarator.getElementNum();
            this.emit(Instruction.SIPUSH, ""+num);
            this.emit(Instruction.NEWARRAY , type);
            int idx = getLocalVariableIndex(symbol);
            this.emit(Instruction.ASTORE, "" + idx);
        }

    symbol对应的就是变量a的Symbol对象,arrayNameMap的定义如下:

    private Map<String, String> arrayNameMap = new HashMap<String, String>();

    哈希表的key对应的是变量的作用域,哈希表的值对应变量名,由于上面代码中,变量a的作用域是”main”, 它的变量名是”a”,因此代码会在哈希表中加入一条记录(“main”,”a”)。代码执行是首先判断该变量对应的数组是否已经生成过,如果生成过,那就直接返回,如果没有生成过,那么先判断输入的变量是否属于数组类型,如果是,那就获取数组的类型,接着通过delcarator.getElementNum()获得数组的元素个数,通过getLocalVariableIndex()获得变量在队列中的位置,然后根据前面讲解过的数值生成指令,把相关指令输出到java汇编代码文件中。

    我们再看看读取数组元素的实现:

    public void readArrayElement(Symbol symbol, int index) {
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); 
            if (declarator == null) {
                return;
            }
    
            int idx = getLocalVariableIndex(symbol); 
            this.emit(Instruction.ALOAD, ""+idx);
            this.emit(Instruction.SIPUSH, ""+index);
            this.emit(Instruction.IALOAD);
        }

    根据前面的理论,我们先通过aload指令把数组对象从队列加载到堆栈上,接着把要读取的元素下标压入队列,最后通过iaload指令把元素的值从数组中加载到堆栈上。

    接着是修改元素值的实现:

    public void writeArrayElement(Symbol symbol, int index, Object value) {
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); 
            if (declarator == null) {
                return;
            }
    
            int idx = getLocalVariableIndex(symbol); 
            if (symbol.hasType(Specifier.INT)) {
                int val = (int)value;
                this.emit(Instruction.ALOAD, ""+idx);
                this.emit(Instruction.SIPUSH, ""+index);
                this.emit(Instruction.SIPUSH, ""+val);
                this.emit(Instruction.IASTORE);
            }
        }

    数组元素写入的实现跟我们前面的理论描述是一致的,先是通过指令aload把数组对象加载到堆栈,然后把要写入的元素下标压入堆栈,最后把要写入元素的值压入堆栈,接着执行iastore指令,把相关信息写入数组的相应元素。

    当编译器对代码进行解析时,遇到数组的读写,例如解析到语句a[3] = 10;时,在UnaryNodeExecutor.java中的以下代码会被调用:

    case CGrammarInitializer.Unary_LB_Expr_RB_TO_Unary:
                child = root.getChildren().get(0);
                symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
    
                child = root.getChildren().get(1);
                int index = (Integer)child.getAttribute(ICodeKey.VALUE);
    
                try {
                    Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);
                    if (declarator != null) {
                        Object val = declarator.getElement(index);
                        root.setAttribute(ICodeKey.VALUE, val);
                        ArrayValueSetter setter = new ArrayValueSetter(symbol, index);
                        root.setAttribute(ICodeKey.SYMBOL, setter);
                        root.setAttribute(ICodeKey.TEXT, symbol.getName()); 
    
                        //create array object on jvm
    ProgramGenerator.getInstance().createArray(symbol);
                        ProgramGenerator.getInstance().readArrayElement(symbol, index);
                    }

    数组元素的读写对应的语法表达式是:

    UNARY -> UNARY LB EXPR RB

    执行该表达式的正是上面给定的代码,在读写数组元素时,我们先调用createArray在jvm的堆栈上生成数组对象,再调用readArrayElement来读取数组中给定元素的值。

    编译器中负责修改数组元素的部分是ArrayValueSetter类,所以我们也在里面进行相应指令的输出,代码如下:

     public void setValue(Object obj) {
            Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);
            try {
                declarator.addElement(index, obj);
    
                ProgramGenerator.getInstance().writeArrayElement(symbol, index, obj);
                System.out.println("Set Value of " + obj.toString() + " to Array of name " + symbol.getName() + " with index of " + index);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                System.err.println(e.getMessage());
                e.printStackTrace();
                System.exit(1);
            }
    
        }

    编译器在解读数组元素的修改语句时,顺便调用writeArrayElement生成jvm上对数组元素进行修改的指令。

    上面代码完成后运行,编译器把给定的C语言代码编译成如下的java汇编语言代码:

    .class public CSourceToJava
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
        sipush  3
        sipush  10
        newarray    int
        astore  0
        aload   0
        sipush  3
        iaload
        sipush  10
        aload   0
        sipush  3
        sipush  10
        iastore
        sipush  3
        aload   0
        sipush  3
        iaload
        istore  1
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "value of a[3] is :"
        invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        iload   1
        invokevirtual   java/io/PrintStream/print(I)V
        getstatic   java/lang/System/out Ljava/io/PrintStream;
        ldc "
    "
        invokevirtual   java/io/PrintStream/print(Ljava/lang/String;)V
        return
    .end method
    .end class
    

    把上面java汇编代码编译成二进制字节码,运行在虚拟机上的结果如下:
    这里写图片描述

    通过运行结果可见,我们编译器的实现是正确的。

    还需要提一下的是,我们的编译器在编译的时候,产生了冗余语句,因为编译器在解析源码时,一旦遇到数字字符串,它就会生成一条把对应数字进行压栈的语句,在上面java汇编代码中,第一条语句:

    sipush 3

    其实就是冗余语句,它是完全没有必要的,出现这条语句的原因是,当我们的编译器在解析语句a[3] = 10;的时候,读取到字符3的时候,它不管三七二十一,立马产生一条将常量压入堆栈的语句,也就是上面那条语句。解读数值常量的代码也是在UnaryNodeExecutor.java中,代码如下:

    case CGrammarInitializer.Number_TO_Unary:
            ....
                ProgramGenerator.getInstance().emit(Instruction.SIPUSH, "" + value);
                break;

    正是上面的代码导致编译器一旦解读到数值常量就立马输出一条sipush指令,虽然冗余语句不会对编译结果造成影响,但是它会让我们生成的最终代码在运行上的速度下降。

    正因为这个原因,在解读语句a[3]=10;时,读取到最后的数值10时,编译器又会生成一条冗余指令,也就是第12行的sipush 10.由于冗余语句的存在,会使得最终生成的java汇编代码与预想的多了一些指令,大家把结果编译出来后,读取最终Java汇编代码时,注意不要被迷惑,后面我们会想办法处理冗余指令这个问题。

    更详细的讲解和代码演示,请参看视频。

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • Java代码转化字节码Class

    千次阅读 2020-01-13 22:13:45
    编译:Java代码转化字节码class 低级语言是计算机认识的语言、高级语言是程序员认识的语言。那么如何从高级语言转换成低级语言呢?这个过程其实就是编译。从java源码到class文件是为了后面可以加载到jvm中,这个...
  • 从一个class文件深入理解Java字节码结构

    万次阅读 多人点赞 2018-05-15 10:01:56
    我们都知道,Java程序最终是转换成class文件执行在虚拟机上的,那么class文件是个怎样的结构,虚拟机又是如何处理去执行class文件里面的内容呢,这篇文章带你深入理解Java字节码中的结构。 1.Demo源码 首先,...
  • 我们把C代码编译成java字节码,这样我们的C语言便具备了可跨品台属性。通过把C语言编译成java字节码,我们不但能够继续学习和掌握编译原理相关的算法技术,于此同时,还能深入理解java虚拟机的基本原理,此乃...
  • 文章目录前言从AOP说起静态代理动态代理JavaProxyCGLIB字节码增强实现AOPASMJavaAssist运行时类加载...在上篇文章Java字节码技术(一)中已经介绍了Java中字节码相关的基础概念。我们知道,Java代码转换后的JVM指令...
  • Java字节码增强探秘

    千次阅读 2019-09-10 14:03:58
    1.字节码 ...因此,也可以看出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用ja...
  • Java字节码与Dalvik字节码

    千次阅读 2016-07-13 12:40:46
    JVM只与字节码关联,而不与Java语言直接关联。事实上,JRuby,Groovy等语言也可以由相应的编译器编译为字节码,然后由JVM解释执行。甚至可以自己写一个class字节码文件,然后由JVM来执行。 2. class类文件结构 任何...
  • public static void main(String[] args)throws Exception { String msg="汉字"; byte[] bytes=msg.getBytes("utf-8"); String ms=new String(bytes,0,bytes.length,"utf-8"); System.out.print(ms);...
  • 什么是Java字节码

    千次阅读 2018-11-18 22:10:14
    Java号称是一门“一次编译到处运行”的语言,从我们写的java文件到通过编译器编译成java字节码文件(.class文件),这个过程是java编译过程;而我们的java虚拟机执行的就是字节码文件。不论该字节码文件来自何方,由...
  • Java字节码文件

    千次阅读 2019-07-02 20:27:34
    字节码文件信息 常量池 方法表集合 二、分析try-catch-finally的demo 计算机只认识0和1。这意味着任何语言编写的程序最终都需要经过编译器编译机器码才能被计算机执行。所以,我们所编写的程序在不同的平台上...
  •  ASM是一个java字节码操纵框架,它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .
  • 跟随本篇文章深入研究JVM内部结构和java字节码,你将会知道如何分解你的文件进行深入检查。 对于一个经验丰富的开发人员阅读java字节码也是非常枯燥的事情。首先我们要弄清楚我们为什么需要知道如此底层的东西?...
  • 要理解java字节码的运行情况,首先要了解有关JVM的一些知识,这些是java字节码运行的先决条件。 JVM数据类型 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是什么 ...
  • def pb2jb(byte_arr): ... python字节码转java字节码 :param byte_arr: :return: """ return [int(i) - 256 if int(i) > 127 else int(i) for i in byte_arr] def jb2pb(byte_arr): """ ...
  • Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京技术学院的数学和计算机科学系的 Shigeru Chiba 所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss...
  • JAVA字节码增强技术之ASM

    千次阅读 2017-03-17 10:07:23
    ASM是一款基于java字节码层面的代码分析和修改工具;无需提供源代码即可对应用嵌入所需debug代码,用于应用API性能分析,代码优化和代码混淆等工作。ASM的目标是生成,转换和分析已编译的java class文件,可使用ASM...
  • Java字节码进制转换

    千次阅读 2016-07-14 16:18:41
    在理解二进制和十进制、十六进制时发现的,作为后续参考保存! ... * @param bin String 我们看到的要转换成十六进制的字符串 * @return */ public static String bin2hex(String bin) {
  • Java 虚拟机具体是怎样运行 Java 字节码的? Java 代码有很多种不同的运行方式,可以在开发工具种运行;可以双击执行 jar 文件运行;可以在命令行种运行;甚至可以在网页种运行,这些都离不开 JRE,也就是 Java运行...
  • Java字节码详解(一) class文件结构

    千次阅读 2018-10-21 16:08:54
    java字节码详解-各个字段属性的意义
  • javac命令可以编译java文件,也可以查看字节码文件 将java文件编译成字节码文件的功能是用java写的,项目为%JAVA_HOME%\lib\tools\com\sun\tools\javac
  • 本文将介绍与操作Java字节码有关的基本知识和操作Java字节码的方法及Demo,谈到操作Java字节码,不能不谈到AOP,这里向大家做一下简单介绍。  你知道如何操作JAVA字节码文件吗,这里将介绍与操作Java字节码有关的...
  • Java字节码介绍及动态修改类

    千次阅读 2018-09-06 18:36:30
    对于Java字节码,它是在Java类的编译过程产生的,即由.java源文件到.class二进制字节码文件的过程。而Java类的加载又是通过类的名字获取二进制字节流,然后在内存中将字节流生成类对象。所以动态修改类的时机在于...
  • Java字节码指令集

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 197,440
精华内容 78,976
关键字:

java字节码转换成java代码

java 订阅