精华内容
下载资源
问答
  • Java字节码增强技术
    千次阅读
    2022-02-12 23:39:07


    字节码

    字节码含义:待补充。

    Java为了能让Java程序编译一次到处运行,用Java 编译器将程序对源代码编译生成固定格式的字节码(.class文件)供JVM使用。

    因此理论上来说,只要符合JVM规范的字节码文件,就可以在JVM上运行,不同的JVM类语言(如Scala、Groovy、Kotlin)编译成字节码都可在JVM运行,除此之外,如果你对JVM的字节码规范非常了解的话,通过自己按照JVM规范自己写也是可以的。

    字节码增强

    简单理解就是通过某种手段或者技术修改编译好的字节码,让新生成的字节码能满足我们的定制需求。

    这里说的需求就有很多了,比如常用的AOP底层很多就是使用字节码增强来达到切面拦截,再比如微服务中的链路追踪就使用了字节码增强(仅仅只一些Java客户端)来进行埋点标记来记录调用链关系的,所以了解字节码增强对一些框架能有更深入对理解,对问题排查有很大对帮助。

    字节码增强技术

    上面说的通过某种手段或者技术到底指哪些呢?我们最常用的Java Proxy也是一种增强技术,另外常用的还有 ASMAspectJJavassist等常用的技术。

    • ASM在指令层次操作字节码的,需要对JVM的指令有一定的了解,同时众多的指令也很难记住,操作比较高;
    • AspectJ扩展了Java,定义了一些专门的AOP语法,其中Spring AOP就使用了AspectJ;
    • Javassist是强调源代码层次操作字节码的框架,操作起来很容易入手。


    参考文章:字节码增强技术-Javassist

    更多相关内容
  • 主要给大家介绍了关于java字节码框架ASM如何操作字节码的相关资料,文中通过示例代码介绍的很详细,有需要的朋友可以参考借鉴,下面来一起看看吧。
  • 我测试了一个前端时间开发的一个网络通讯工具,其中用到了很多java15的特性,例如泛形,增强循环,静态引入等功能,以及jdk15独有的类文件,例如StringBuilder等。通过该工具进行转换,可以完美的运行在java14环境...
  • jclasslib是一款免费开源的java字节码查看工具,该软件不但可以查看java字节码,同时还包含一个类库允许开发者读取,修改,写入Java Class文件与字节码。简单的说:用户可以通过jclasslib修改jar包下面的类,是一个...
  • Java 字节码编辑工具

    2018-10-11 20:39:37
    优秀的Java字节码可视化编辑工具,使用方便上手简单。
  • java字节码反编译工具

    2018-11-08 20:45:15
    是一款反编译软件,JD分为JD-GUI、JD-Eclipse两种运行方式,JD-GUI是以单独的程序的方式运行,JD-Eclipse则是以一个Eclipse插件的方式运行。
  • Java字节码工程工具包 千叶茂(Shigeru Chiba)版权所有(C)1999-2020,保留所有权利。 Javassist(JAVA编程ASSISTant)使Java字节码操作变得简单。 它是一个用Java编辑字节码的类库。 它使Java程序可以在运行时...
  • Java Bytecode Editor是修改和分析java字节码结构和类文件的有效工具:下载解压后,双击jbe. bat(Windows)或jbe.sh(Linux)即可运行JBE。src目录下的是JBE的源码。
  • java字节码编辑器

    2017-01-18 10:49:59
    查看编辑class文件,可查看操作字节码bytecode指令
  • 轻松看懂Java字节码,对java字节码的详细分析,理解java字节码
  • 本篇文章对Java字节码指令集的使用进行了详细的介绍。需要的朋友参考下
  • 1.将class文件直接拖拽进工作框,即可查看class文件. 2.支持目录结构查看,方便实用
  • java字节码加密

    2017-03-13 17:32:16
    java字节码加密
  • Recaf 一个现代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 文件本质上就是一张表,它由下表所示的数据项构成。

    类型名称数量描述
    u4magic1魔数,占用4个字节,offset 0-3
    u2minor_version1次版本号,offset 4-5
    u2major_version1主版本号 ,offset 7-8
    u2constant_pool_count1常量池数量,offset 8-9
    cp_infoconstant_pollconstant_pool_count-1常量池,Class 文件之中的资源仓库。数量不固定
    u2access_flags1访问标志,用于识别一些类或者接口层次的访问信息
    u2this_class1类索引,用于确定这个类的全限定名
    u2super_class1父类索引,用于确定这个类的父类的全限定名
    u2interfaces_count1接口计数器,表示索引表的容量
    u2interfacesinterfaces_count接口索引集合,用来描述这个类实现了哪些接口
    u2fields_count1字段容量计数器,记录这个表含有多少个字段
    field_infofieldsfields_count字段表集合,用于描述接口或者类中声明的变量
    u2methods_count1方法表计数器,记录这个表含有多少个方法
    method_infomethodsmethods_count方法表集合,存放 Class 的方法集合
    u2attributes_count1属性表计数器,表示字段表或方法表有多少个属性
    attributeattributesattributes_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.145.000 00 00 2D
    1.246.000 00 00 2E
    1.347.000 00 00 2F
    1.448.000 00 00 30
    1.549.000 00 00 31
    1.650.000 00 00 32
    1.751.000 00 00 33
    1.852.000 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_info1UTF-8 编码的字符串
    CONSTANT_Integer_info3整型字面量
    CONSTANT_Float_info4浮点型字面量
    CONSTANT_Long5长整型字面量
    CONSTANT_Double_info6双精度浮点型字面量
    CONSTANT_Class_info7类或接口的符号引用
    CONSTANT_String_info8字符串类型字面量
    CONSTANT_Fieldref_info9字段的符号引用
    CONSTANT_Methidref_info10类中方法的符号引用
    CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
    CONSTANT_NameAndType_info12字段或方法的部分符号引用
    CONSTANT_MethodHandle_info15表示方法句柄
    CONSTANT_MethodType_info16标识方法类型
    CONSTANT_InvokeDynamic_info18标识一个动态方法调用点

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

    名称类型描述
    tagu1值为10
    indexu2指向声明方法的类描述符 CONSTANT_Class_info 的索引项
    indexu2指向名称及类型描述符 CONSTANT_NameAndType 的索引项

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

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

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

    名称类型描述
    tagu1数量 1
    lengthu2长度,表示占用几个字节
    bytesu1占用 length 个字节

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

    1.4、访问标志

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

    标志名称标志值含义
    ACC_PUBLIC0x0001是否为 public 类型
    ACC_FINAL0x0010是否被声明为 final,只有类可设置
    ACC_SUPER0x0020是否允许使用 invokespecial 字节码指令的新语意,invokespecial指令的语意在 JDK 1.0.2 发生过改变,为了区别这条指令使用哪种语意,JDK 1.0.2 之后编译出来的类的这个标志都必须为真
    ACC_INTERFACE0x0200标识这是一个接口
    ACC_ABSTRACT0x0400是否为 abstract 类型,对于接口或者抽象类来说,此标志值为真,其他类值为假
    ACC_SYNTHETIC0x1000标识这个类并非由用户代码产生的
    ACC_ANNOTATION0x2000标识这是一个注解
    ACC_ENUM0x4000标识这是一个枚举

    在这里插入图片描述
    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)包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
    字段表结构:

    类型名称数量
    u2access_flags1
    u2name_index1
    u2descriptor_index1
    u2attributes_count1
    attribute_infoattributesattributes_count

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

    标志名称标志值含义
    ACC_PUBLIC0x0001字段是否为 public
    ACC_PRIVATE0x0002字段是否为 private
    ACC_PROTECTED0x0004字段是否为 proctected
    ACC_STATIC0x0008字段是否为 static
    ACC_FINAL0x0010字段是否为 final
    ACC_VOLATILE0x0040字段是否为 volatile
    ACC_TRANSIENT0x0080字段是否为 transient
    ACC_SYNTHETIC0x1000字段是否由编译器自动产生的
    ACC_ENUM0x4000字段是否为 enum

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

    1.7、方法表集合

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

    类型名称数量
    u2access_flags1
    u2name_index1
    u2descriptor_index1
    u2attributes_count1
    attribute_infoattributesattributes_count

    access_flags 方法访问标志:

    标志名称标志值含义
    ACC_PUBLIC0x0001方法是否为 public
    ACC_PRIVATE0x0002方法是否为 private
    ACC_PROTECTED0x0004方法是否为 proctected
    ACC_STATIC0x0008方法是否为 static
    ACC_FINAL0x0010方法是否为 final
    ACC_SYNCHRONIZED0x0020方法是否为 synchronized
    ACC_BRIDGE0x0040方法是否是由编译器产生的桥接方法
    ACC_VARARGS0x0080方法是否接受不定参数
    ACC_NATIVE0x0100方法是否为 native
    ACC_ABSTRACT0x0400方法是否为 abstract
    ACC_STRICTFP0x0800方法是否为 strictfp
    ACC_SYNTHETIC0x1000方法是否由编译器自动产生的

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

    1.8、属性表集合

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

    属性名称使用位置含义
    Code方法表Java 代码编译成的字节码指令
    ConstantValue字段表final 关键字定义的常量值
    Deprecated类、方法表、字段表被声明为 deprecated 的方法和字段
    Exceptions方法表方法抛出的异常
    EnclosingMethod类文件仅当一个类为局部类或者匿名类时才能拥有这个属性,这个属性用于标识这个类所在的外围方法
    InnerClasses类文件内部类列表
    LineNumberTableCode 属性Java 源码的行号与字节码指令的对应关系
    LocalVariableTableCode 属性方法的局部变量描述
    StackMapTableCode 属性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 的长度属性去说明属性值所占用的位数即可,一个符合规则的属性表应该满足下表所定义的结构。
    属性表结构:

    类型名称数量
    u2attribute_name_index1
    u4attribute_length1
    u1infoattribute_length

    1.8.1、Code 属性

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

    类型名称数量描述
    u2attibute_name_index1属性名称,指向 CONSTANT_Utf8_info 型常量的索引,常量值固定为“Code”
    u4attribute_length1属性值长度,由于属性名称索引与属性长度一共为 6 字节,所以属性值的长度固定为整个属性表长度减去 6 个字节
    u2max_stack1操作数栈深度最大值,在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度
    u2max_locals1局部变量表所需的存储空间,max_locals 的单位是 Slot,Slot 是虚拟机为局部变量分配内存所使用的最小单位
    u4code_length1字节码长度
    u1codecode_length字节码指令字节流,用于存储字节码指令的一系列字节流。文章的末尾会给出“虚拟机字节码指令表”。
    u2exception_table_length1
    exception_infoexception_tableexception_table_length
    u2attributes_count1
    attribute_infoattributesattributes_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字节码简单说明
  • 轻松理解 java字节码

    2021-03-22 00:06:18
    一、什么是java字节码 字节码(Bytecode),是一种包含执行程序、由一序列操作码或 数据对 组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码 熟悉的java,.java文件...

    一、什么是字节码

    字节码(Byte-code)是一种包含执行程序、由一序列 op 代码(操作码)/数据对组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。它经常被看作是包含一个执行程序的二进制文件,更像一个对象模型。.class文件,存储的就是字节码。因为是属于特殊格式(不要考虑给人看懂,只面向虚拟机),所以一般打开.class文件看上去是乱码,但对于机器来说是格式明朗的。

    字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

    java字节码是指,java虚拟机(JVM)编译出的字节码。统称为字节码。字节码的存在,实现了 jvm 和编程语言的解耦,jvm 并不是 java 语言的专属,非java语言也可以运行在jvm之上,并可将其对应的源程序编译成字节码。严格地说并不能把 “字节码” 称为 “java字节码”, 字节码和java没有必然联系。JVM 上承开发语言,下接操作系统,它的中间接口就是字节码。每个平台(不同操作系统)的机器码都不同。java虚拟机实现了对所有不同机器环境的适配,即将所有机器的不同机器码规则做了一个统一的接口(适配器)—— 不同机器装入jvm,接收其他机器发送的字节码后,字节码在jvm上直接运行,以适配不同机器环境的差异。

    如果纯粹为了跨平台,其实根本不需要jvm,直接把源码打包就行了。到了不同平台,在运行时直接把源码进行编译就OK了。但这种从源码开始的编译,速度非常慢。因此,出于性能的考虑,先将源码做一些预处理,处理为字节码,来减轻运行前的编译的性能开销。所以不要理解为有jvm才能跨平台。jvm是为了更好的跨平台

    二、采用字节码好处

    采用字节码的好处:
    Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。

    三、java中的编译器和解释器

    Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种供虚拟机理解的代码叫做字节码(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这就是上面提到的Java的特点的编译与解释并存的解释。

    Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。

    1. 在最早的 java发布时,执行引擎原理:解析 字节码指令,转换为调用对应的 c语言函数实现。例如: if(opcode等于xx001) { add(a,b); } , xx001是字节码指令,add(a,b) 是c语言实现的函数。

    2. 后来由于直接翻译为c语言再去执行效率太低,为了提高执行效率,干脆直接将字节码指令翻译为机器码(不经过c了),然后将pc寄存器指向那块临时生成的机器码内存区域起始地址,交给cpu执行 ,在c语言里使用函数指针可以实现这个目标(在c语言里可以实现直接执行汇编代码或者机器码)。

    3. 再后来出现优化热点代码的技术,比如 for 循环 这种场景,我干脆把第一次for循环里面的指令生成的机器码缓存起来,重复利用,不用每次分配内存生成重复机器码,回收内存了。

    四、如何运行字节码?

    当源代码转化为字节码之后,其实要运行程序,有两种选择。一种是 使用 Java 解释器解释执行字节码,另一种则是 使用 JIT 编译器将字节码转化为本地机器代码

    这两种方式的区别:

    前者启动速度快但运行速度慢,而后者启动速度慢但运行速度快。

    至于为什么会这样,其原因很简单。因为解释器不需要像 JIT 编译器一样,将所有字节码都转化为机器码,自然就少去了优化的时间。而当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。所以在实际情况中,为了运行速度以及效率,我们通常采用两者相结合的方式进行 Java 代码的编译执行。

    展开全文
  • org.openjdk.asmtools.jdis.Main Java字节码的反汇编器工具 Java字节码的反汇编器工具
  • javassist, Java字节码工程工具包 Java字节码工程工具包 版本 3版权所有( C ) 1999 -2017按 Shigeru Chiba,保留所有权利。Javassist ( Java编程助手) 使Java字节码操作简单。 它是一个类库,用于在Java中编辑字节码
  • 一种有效保护Java字节码的方法
  • JavaBytecodeAnalyzer 解析 Java 字节码,学习字节码指令集 解析 class 文件为字节码,以熟悉 class 文件结构 学习 java bytecode 指令集
  • 关于Java字节码

    千次阅读 2018-09-24 10:25:23
    关于Java字节码 概述 从写Java文件到编译成字节码文件(也就是.class文件)的过程也就是Java文件编译的过程,我们所写的是Java文件而Java虚拟机编译的是字节码文件 class文件格式 ...

    关于Java字节码

    1. 概述

    从写Java文件到编译成字节码文件(也就是.class文件)的过程也就是Java文件编译的过程,我们所写的是Java文件而Java虚拟机编译的是字节码文件

    2. class文件格式

    class文件格式

    3. 举个栗子来说明一下

    ①先写一个.java文件
    

    Demo的java文件

    ②打开.class文件
    

    Demo的class文件

    3.1 magic 魔数

    前四个字节为魔数,值为:0xCAFE BABE Java创始人 James Gosling制定 用来表示是class 文件 有人会问为什么为cafebaby,联想一下java的咖啡杯图标就知道了。

    3.2 minor_version & major_version 版本号

    minor_version & major_version对应 的16进制为 0000 0034 前两个字节为minor_version 为此版本号 后两个字节为主版本号。所有该文件的版本号为1.8。

    版本号JDK版本号
    2eJDK1.2
    2fJDK1.3
    30JDK1.4
    31JDK1.5
    32JDK1.6
    33JDK1.7
    34JDK1.8

    3.3 常量池

    在版本号之后的为常量池,常量池可以理解为Class文件中的资源仓库。他是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项之一,同时它还是在Class文件中第一个出现的表类型数据项目。由于常量池中的常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count),这个计数是从1开始而不是从0开始的
    常量池主要存放两大类字面量和符号引用,字面量如文本字符串,声明为final的常量值等,符号引用属于编译原理方面的概念包括三种常量:①类和接口的权限定名 ②字段的名称和描述符 ③方法名和描述符
    常量池每一项常量都是一个表,jdk1.7之前有11种结构不同的表结构数据,在jdk1.7中又增加了三种下图为14种常量类型所代表的具体含义
    常量所代表的具体含义

    在魔数和版本号后面为常量池,0x0016 为常量池容量,十进制为22,这就代表了常量池中有21项常量,索引值范围为1~~21。在Class 文件格式规范制定的时候,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何常量池项目的含义”这种情况就可以吧索引置为0来表示。
    0x07查表的标志列可以知道这个常量属于CONSTANT_Class_info类型 此类型代表一个类或者接口的符号引用。
    CONSTANT_Class_info类型比较简单具体结构看下图:
    CONSTANT_Class_info的结构
    tag是标志位,它用于区分常量类型。name_index 是一个索引值 在这里值为0x0002 也即是指向了常量池里的第二个常量
    0x01为常量池中的第二个常量根据标志列可知这个常量为CONSTANT_Utf8_info类型的常量,下图为常量结构

    CONSTANT_Utf8_info常量的结构
    length值说明了这个utf-8编码的字符串长度是多少字节,在这里为0x013表示19个字节,他后面紧跟着的长度为length字节的连续数据是一个使用utf-8缩略编码表示的字符串。
    636f~~~6f 19个字节表示的是使用utf-8缩略编码表示的字符串。翻译完为:com/baidu/test/demo
    我们现在已经分析了两个常量,后续的19个都可以这样分析完成但是Oracle公司给我们提供了一个专门用于分析class文件字节码的工具:Javap 具体如下图
    通过javap解析出的常量池
    从图中可知计算机已经帮我们吧整个常量池都计算了出来。

    3.4 access_flage 访问标志

    在常量池结束后,紧接着的两个字节代表访问标志 access_flags,这个标志用于识别一些类或者接口层测的访问信息,比如:是类还是接口,是否定义为public,是否定义为abstract类型,如果是类的话是否被声明为final等等下面是具体的含义表

    访问标准的具体含义表
    在这里具体值为0x0021 0x0021为0x0021和0x0001的与运算结果,为啥要进行与运算呢,因为access_flags 中共有16个标志位可以使用,当前只定义了8个,没有使用到的标志要求为0,Demo是一个普通java类,不是接口、枚举、或者注解,只有public关键字修饰

    3.5 this_class & super_class &interaces 类索引、父类索引与接口索引集合

    this_class(类索引)、super_class(父类索引)都是一个u2类型的数据,而interfaces(接口索引集合)是一组u2类型的数据的集合class文件由这三个数据来确定这个类的继承关系
    Demo的class文件
    0x0001、0x0003、0x0000 分别表示类索引为1 、父类索引为3、接口索引集合大小为0
    通过前面用javap算出来的常量池可以知道
    类索引为:com/baidu/test/Demo;
    父类索引为: java/lang/Object;

    3.6 field_info字段表集合

    字段表用于描述接口或者类中声明的变量,但是不包括在方法内部声明的局部变量。可以包括的信息有:public、private、protected、static、final、volatile、transient、基本类型、对象、数组、字段名称
    下图为字段表的格式
    字段表结构
    字段访问标注如下图
    字段访问标志
    Demo的class文件
    0x0001 为fields_count
    0x0002 为access_flags
    0x0005 为name_index
    0x0006 为descriptor_index

    3.7 methods 方法表集合

    方法表集合与字段表集合的结构完全一致,结构如下
    在这里插入图片描述
    在访问标志和属性集合的可选项中有所不同,下图为方法访问标志
    在这里插入图片描述
    有人会提出疑问,不知道方法中的代码去哪里了,方法中的Java代码经过编译器编译成字节码指令后存放到方法属性表的一个名为code的属性中了,在下面属性表会进行讲解在这里插入图片描述
    0x0002:methods_count
    0x0001:access_flags
    0x0007:descriptor_index
    0x0008:attribute_count
    0x0009:name_index

    3.8attribute_info属性表集合

    为了能正确解析 class文件,《Java虚拟机规范(第二版)》中预定了9项虚拟机实现应当能识别的属性,而在最新的《java虚拟机规范(java SE 7)》版本中,预定义属性已经增加到了21项,见下图。

    虚拟机规范预定义的属性
    接下来对其中一些关键字段进行讲解
    (1).code
    Java程序方法体中的代码经过javac编译器处理之后,最终变为字节码指令存在code属性内。code属性表结构如下:
    code属性表结构
    attribute_name_index : 是项指向CONSTANT_Utf8_info 型常量的索引,常量值固定为“code”,他代表该属性的名称;
    attribute_length:指示了属性的长度,由于属性名称索引与属性长度一共为6个字节,所以属性值的长度固定为整个属性表长度减去6个字节。
    max_stack 代表了操作数栈深度的最大值,虚拟机运行的时候需要根据这个值来分配栈帧中的操作栈深度。
    max_locals:代表了局部变量表所需的存储空间。
    code_length:和code用来存储java源程序编译后生成的字节码指令。code_length 表示字节码的长度,code是用于存储字节码指令的一系列字节流。
    code 属性是class文件中最重要的一个属性,如果把java程序中的信息分为代码和元数据两部分,那么在整个class文件中,code属性用于描述代码(方法体内的),所有的其他数据项目都用于描述元数据。
    (2). exceptions属性
    exception属性的作用列举方法中可能抛出的受查异常的(Checked Exceptions)也就是方法描述时在throws关键字后面列举的异常。它的结构见下表
    exceptions属性表结构
    (3).LineNumberTable 属性
    LineNumberTable 属性用于描述java源代码行号与字节码行号之间的对应关系。属性表结构如下。
    在这里插入图片描述
    (4).LocalVariableTable属性
    LocalVariableTable属性用于描述栈帧中局部变量表中的变量与java源码中定义的变量之间的关系。属性表结构见下图。
    LocalVariableTable属性表结构
    (6).SourceFile属性
    SourceFile属性用于记录生成这个class文件的源码文件名称。结构见下图。
    SourceFile属性表结构
    (6).ConstantValue属性
    ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static修饰的变量才能使用这个属性。结构见下图。
    ContantValue属性表结构
    (7).InnerClasses属性
    InnerClasses这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证其使用。结构见下表
    InnerClasses属性表结构
    (8).Depercated & Synthetic
    Depercated & Synthetic 两个属性的属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念。结构加下图
    Depercated & Synthetic属性结构

    4总结

    class文件是java虚拟机执行引擎的数据入口,也是java技术体系的基础构成之一,了解class文件的结构对进一步了解虚拟机执行引擎有很重要的意义。

    展开全文
  • Java字节码指令列表

    热门讨论 2011-05-29 00:05:10
    Java字节码指令列表,列出了每条指令的操作码和操作数,和对栈的操作情况
  • java字节码分析工具

    2009-07-11 21:25:42
    Java字节码分析工具,系统分析了java字节码文件,即java class类文件,对该文件中的各种成分以树的形式描述出来,只能针对未加密的class文件,一般由标准java编译器编译生成的class文件都未加密,该系统在vs2003下面...
  • Java 字节码和 Dalvik 字节码概述源代码样例Java 字节码Dalvik 字节码总结 概述   本篇博客将讲述 Java 源代码到字节码字节码转汇编,以及 Android 中 Java 源代码转 Dalvik 字节码,Dalvik 字节码转 smali 汇编...
  • 字节码器是一个丰富的领域模型,用于Java字节码和框架解释并将其传输给其他人
  • 什么是Java字节码

    千次阅读 2022-04-13 09:35:52
    因此,也可以看 出字节码对于Java生态的重要性。之所以被称之为字节码,是因为字节码文件由十六进制值组成,而 JVM以两个十六进制值为一组,即以字节为单位进行读取。在Java中一般是用javac命令编译源代码为字 节码...
  • Java字节码增强探秘

    千次阅读 2019-09-10 08:48:00
    本文转载自公众号美团技术团队大家好,美美今天给大家推荐一篇Java字节码增强技术的文章,在实际工作中有很多应用场景。美团点评技术团队平日积累了很多这类技术原理解析和实...
  • BinEd:以16进制格式查看class文件使用方法:右键class文件,点击Open as binaryJClassLib:以一种更为方便的方式查看字节码,相当于javap -v HelloWorld.class使用方法:光标锁定在java源文件-->选IDEA上方菜单--&...
  • 从一个class文件深入理解Java字节码结构

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 499,326
精华内容 199,730
关键字:

java字节码

java 订阅
友情链接: 图片播放器.rar