精华内容
参与话题
问答
  • 1. Class文件结构简介 2、常量池 2.1 UTF-8 编码的字符串 CONSTANT_Utf8_info 2.2 整数 CONSTANT_Integer_info 2.3 类信息 CONSTANT_Class_info 2.4 字符串字面量 CONSTANT_String_info 2.5 字段引用信息 ...

    目录

     

    1. Class文件结构简介

     2、常量池

    2.1 UTF-8 编码的字符串 CONSTANT_Utf8_info

    2.2 整数 CONSTANT_Integer_info 

    2.3 类信息 CONSTANT_Class_info

    2.4 字符串字面量 CONSTANT_String_info

    2.5 字段引用信息 CONSTANT_Fieldref_info

    2.6 方法引用信息 CONSTANT_Methodref_info

    2.7 接口方法引用信息 CONSTANT_InterfaceMethodref_info

    2.8 名称及类型信息 CONSTANT_NameAndType

    3、访问标志Access_flag

    4. 类索引(This_class),父类索引(Super_class),接口索引集合(Interfaces)

    5、字段集合 fields[ count ]

    6、方法集合 methods[ count ]

    7、属性表集合


    1. Class文件结构简介

    不管是大端模式还是小端模式,cpu在内存的读写数据都是从低地址开始到高地址; 大端模式就是先读到的是高位(低地址存高位,高地址存低位),小端模式就是先读到的是低位(低地址存低位,高地址存高位)。

    大端小端产生的根本原因:在计算机中,内存之间不能之间传输数据,需要通过寄存器转,内存的存储单元是字节,但是寄存器是多字节的,于是就有了高位、低位之分,不同的厂家生产的CPU的标准不同,有的用大端(绝大多数),有的用小端,大端小端也就随之而来。

    JDK的bin目录下,有个javap的工具,可以在命令行窗口中利用此工具显示出Class文件的结构,便于分析class字节码文件。

    class文件结构简介:

     class文件的结构就如上图所示,固定顺序,固定格式,以字节为单位,字节之间不存在任何空隙,对于超过8位的数据,将高位放在class文件的前面,低位放在后面(这是大端模式),否则jvm无法正确解析class文件。

     magic:魔数,u4表示4个字节,就是一个文件类型的标志,表明此文件是哪种类型,png、TXT,等等,java class文件的魔数固定是0xCAFEBABE,虚拟机加载class文件时,会先检查这个部分,如果不是这个值,虚拟机拒绝加载该文件。

    minor_version:次版本号;

    major_version:主版本号; 主次版本号构成版本号,class文件的版本号对应着jdk的版本号,比如:45(1.1)、46(1.2)、47(1.3)、48(1.4)、49(1.5)、50(1.6)、51(1.7),可以看到jdk版本每增加0.1,版本号就加1,低版本JDK编译生成的class文件,可以被高版本的JRE执行,但是反之,则不行,虚拟机加载class文件之前,会先查看class文件的版本是否在自己的支持范围之内。

    constant_pool_count:常量池的大小,从1开始计数,比如count = 11,那么元素共10个,1~10,第0个单元空出来;

    constant_pool:常量池,是数组形式,由字面常量 和 符号引用 组成,保存字面常量和符号引用,符号引用保存的是引用的全局限定名,所以保存的是字符串;

    access_flags:当前类的访问权限;

    this_class:当前类的全局限定名在常量池中的索引(常量池以数组方法存储,索引即为下标);

    super_class:当前类的父类的全局限定名在常量池中的索引;

    interfaces_count:当前类实现的接口数目;

    interfaces[ interfaces_count ]:这些接口的全局限定名在常量池中的索引的数组;

    fields_count:字段的数量;

    fields[ fields_count ]:

    结构图如下:

    重点说明:

     用下面这个类的代码作为分析的例子:

    public class Test{
        public int a = 3;
        static Integer si = 6;
        String s = "Hello world!";
        public static void main(){
            Test test = new Test();
            test.a = 8;
            si = 9;
        }
        private void test(){
            this.a = a;
        }
    }

     2、常量池

                  

                                                                            上图是常量池的结构;

    常量池主要存放字面量 和 符号引用,符号引用包括:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

    常量池的存储形式可理解为数组,每个元素又是一个结构(标识(u1类型,一个字节),元素内容),每个元素都有一个索引值,通过这个索引值就可以定位数组中的某个元素,元素的类型有很多,通过标识位来区分,如下表(可以理解常量池主要存储字面量和符号引用了吧!):

                  

    上表是元素类型表,常量池每个元素的具体结构如下表,下表中那些引用结构中,index为索引,索引就是常量池中的数组下标。

             

              

    好,开始分析Test类的class文件(下面用16进制表示)中的常量池内容:

    07  00 02  01 00 04 54 65 73 74 但看这段,07代表类或者接口的符号引用,index占2字节,值为2,代表这是类,不是接口,接着 01代表字符串,length为04,后面4个字节为字符串内容0x54657374 编码过来就是“Test” 。这句 class Test。

    其它的类似这样。

    我们一个一个来分析每种类型。

    2.1 UTF-8 编码的字符串 CONSTANT_Utf8_info

    下图是它的数据结构,第一个是类型标识(1个字节),第二个是这个字符串的长度是多少字节(2个字节),因此变量名、方法名、类名、接口名、字符串常量的最大长度不能超过65535个字节,因为u2能表示的最大数就是65535 bytes = 64KB - 1byte,第三个参数就是字符串的内容了。

    2.2 整数 CONSTANT_Integer_info 

    第一个参数是类型标识(1个字节),如果虚拟机读到类型是3,就知道它是Int类型,默认读取随后的4个字节,第二参数就是int值(4个字节)。

    Float、Long、Double类型的都是一个道理。

    2.3 类信息 CONSTANT_Class_info

    用于描述类名或者接口名,第一个参数是类型标识(1个字节),第二参数是常量池索引,指向第几个常量项,这个常量项存的就是类名(其实就是utf-8字符串常量来存储类名)。

    2.4 字符串字面量 CONSTANT_String_info

    用于描述字符串字面量,比如“heheh”,第一个参数是类型标识(1个字节),第二个参数是常量池索引,指向的常量项存储着字符串字面量的全限定名(还是UTF-8字符串常量来存储的)。

    2.5 字段引用信息 CONSTANT_Fieldref_info

    用于描述类的成员变量的引用信息,第一个参数是类型标识,第二个参数是索引,指向自己的类的CONSTANT_Class_info常量项,第三个参数是索引,指向名称及类型描述符CONSTANT_NameAndType常量项。

    2.6 方法引用信息 CONSTANT_Methodref_info

    用于描述类的成员方法的引用信息,第一个参数同上,第二参数是索引,指向自己的类的CONSTANT_Class_info常量项,第三个参数是索引,指向名称及类型描述符CONSTANT_NameAndType常量项。

    2.7 接口方法引用信息 CONSTANT_InterfaceMethodref_info

    用于描述接口的成员方法的引用信息,第一个参数同上,第二参数是索引,指向自己的接口的CONSTANT_Class_info常量项,第三个参数是索引,指向名称及类型描述符CONSTANT_NameAndType常量项。

    2.8 名称及类型信息 CONSTANT_NameAndType

    用于描述某个内容的名称和类型的信息,第一个参数同上,第二个参数是索引,指向存储该内容的名称的常量项(utf-8常量项),第三个参数是索引,指向该内容的类型描述的常量项(utf-8常量项)。

    3、访问标志Access_flag

    常量池结束后就是访问标志的内容,2个字节,这个标志用于表示一个类或者接口的访问信息,包括:这个是类还是接口,是否定义为public类型,是否定义为abstract类型,是否被声明为final等,各种标志的值如下:

    这些值是16进制表示的,仔细想,占据32位,每一种标志指示占用了其中一个bit(位)。

    所有如果这个类或者接口同时又上表中的多个属性,那么只需将相应的bit置为1即可。

    4. 类索引(This_class),父类索引(Super_class),接口索引集合(Interfaces)

    This_class 是一个2字节的索引,代表本类(接口)的全限定名,指向的是常量池中CONSTANT_Class_info。

    Super_class 是一个2字节的索引,代表本类的父类,仅仅是父类,指向常量池中CONSTANT_Class_info,因为java类最多只有1个父类,没有继承某个类的话,默认就是Object类,如果本类是个接口,那么这个Super_class为0,因为接口是不可能有父类的,接口只能有父接口。

    Interfaces 是接口集合,用于表示一个类实现了哪些接口,或者一个接口继承了哪些接口,分为两部分:interfaces_count 接口数量(2个字节)、interfaces[ ] 接口数组(存的就是那些接口),如果接口数量为0,那么后面就没有接口数组了。

    5、字段集合 fields[ count ]

    字段集合,我习惯叫做字段数组,用于描述接口或者类中的变量,包括类级变量或者实例级变量,不包括在方法内声明的变量。可以描述的信息有:变量的作用域(public、private、protected)、是类级变量还是实例级变量(static 修饰符)、可变性(final)、并发可见性(volatile)、可否序列化(transient)、变量数据类型(基本类型、对象、数组)、变量名称。

    字段集合中不会列出从超类、父接口中继承来的变量,但是一个内部类的字段集合中会自动添加使用到外部类的那些变量。

    fields_count 表示变量的数量,随后就存放多个变量的信息,类似数组一样,变量是有结构的,结构如下表:

    access_flags:和字节码文件中的Access_flags非常类似,用于表示一个变量的访问属性(public、protected、private、static等等),如下表:每个标志类型占据32位中的一位,拥有什么访问属性,就将该bit置为1。

    name_index:变量的简单名字(不是全局限定名那种,就是名字而已,比如int aa = 5;简单名字就是aa,void ss(){ },简单名字就是ss)的索引,索引就是常量池的下标,变量的简单名字存在常量池;

    descriptor_index:变量的描述符的常量池索引,(描述符就是描述变量的数据类型、方法的参数列表、方法的返回值)此处就是变量的数据类型,变量的数据类型也存储在常量池中;这些基本数据类型都用一个大写字符来表示,只有对象类型是用大写字符L加上对象的全限定名加上分号来表示,具体如下表:

    标识符 含义
    B 基本类型byte
    C 基本类型char
    D 基本类型double
    F 基本类型float
    I 基本类型int
    J 基本类型long
    S 基本类型short
    Z 基本类型boolean
    V 特殊类型 void
    L对象全限定名

    对象类型,比如Ljava/lang/String;

    注意不要忘了分号“;” 

    数组类型如何表示呢?是几维数组,就在前面加几个“ [ ”符号,比如 double[ ]  aaa; 那么在常量池中存的描述符就是 [D,  String[ ][ ]  bbb;那么常量池中的描述符就是[[Ljava/lang/String。   

    attributes_count:属性表的属性(额外信息)的数量;

    attributes[ attributes_count ]:属性表,每个元素又是一个结构(这个就复杂了,本文没给出详解);

    6、方法集合 methods[ count ]

    方法集合和字段集合非常类似,基本一样,只是描述符和access_flags的内容不一样,方法的描述符按照参数列表、返回值的顺序描述,方法的参数按照顺序放在“()”里,返回值的类型就放在括号后面,比如 void aaa() 的描述符“()V”,int hehe(char[] aa, String bbb) 的描述符是“([CLjava/lang/String)V”。

    Test类中显示定了两个方法,但是class文件中的methods_count =4 ;因为编译器默认为Test类添加了void <clinit>()方法,这个方法是初始化静态变量和静态块的,还添加了一个默认的无参构造函数,所以methods_count = 4。

               

    access_flag的信息如下:

    那方法里的java代码在class字节码文件里存在哪里呢?存储在属性表attributes里一个名为“Code”的属性里。

    注意:java语言里,对于方法,只有返回值类型不同的话,不能称为重载,会报错,但是在class字节码文件中,只要方法的描述符不相同,这些方法就能共存,换句话说,就是即便两个方法只有返回值类型不同,也能在class字节码文件中共存。

    7、属性表集合

    class文件有属性表、字段集合中有属性表、方法集合中有属性表。

    java虚拟机规定了有哪些属性,随着java的发展,规定的属性还在增加,虚拟机会忽略掉不认识的属性。下面只给了9个属性:

    这些属性就不详解了,只有方法有的属性,在属性表中才会出现。

     

     

     

    展开全文
  • class文件结构

    2020-11-15 12:13:16
    Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器。...

    上一篇

    Java编译器

    Java源代码的编译结果是字节码,那么肯定需要有一种编译器能够将Java源码编译为字节码,承担这个重要责任的就是配置在path环境变量中的javac编译器。javac是一种能够将Java源码编译为字节码的前端编译器。
    前端编译器并不会直接涉及编译优化等方面的技术,而是将这些具体优化细节移交给HotSpot的IT编译器负责。

    解读class(虚拟机的基石)文件的三种方式

    字节码文件

    源代码经过编译器编译之后便会生成一个字节码文件(一个类一个文件),字节码是一种二进制的类文件,它的内容是JVM的指令,而不像c、c++经由编译器直接生成机器码。

    字节码指令

    Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数( operand)所构成。虚拟机中许多指令并不包含操作数,只有一个操作码。
    在这里插入图片描述
    方式二:使用javap指令:jdk自带的反解析工具
    方式三:使用IDEA插件: jclasslib 或jclasslib bytecode viewer客户端工具。(可视化更好)

    class文件结构

    class类的本质

    任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,class文件实际上它并不一定以磁盘文件的形式存在class文件是一组以8位字节为基础单位的二进制流。

    class文件格式

    class的结构不像XML等描述语言,由于它没有任何分隔符号。所以在其中的数据项,无论是字节顺序还是数量,都是被严格限定的哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变。

    Class文件格式采用一 种类似于C语言结构体的方式进行数据存储,这种结构中只有两种数据类型:无符号数和表
    ●无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
    表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“info" 结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。由于表没有固定长度,所以通常会在其前面加上个数说明

    结构概述

    在这里插入图片描述

    Class文件的结构并不是一成不变的,随着Java虚拟机的不断发展,总是不可避免地会对Class文件结构做出一些调整, 但是其基本结构和框架是非常稳定的。
    Class文件的总体结构如下:
    魔数
    Class文件版本
    常量池
    访问标志
    类索引,父类索引,接口索引集合
    字段表集合
    方法表集合
    属性表集合
    在这里插入图片描述

    class文件的标识:魔数

    ●每个Class 文件开头的4个字节的无符号整数称为魔数(Magic Number)
    ●它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件。即:魔数是Class文件的标识符。
    ●魔数值固定为0XCAFEBABE。 不会改变。
    ●如果一个Class文件不以BxCAFEBABE开头,虚拟机在进行文件校验的时候就会直接抛出错误
    ●使用魔数而不是扩展名来进行识别主要是基于安全方面的考虑,因为文件扩展名可以随意地改动。

    class文件版本号

    ●紧接着魔数的4个字节存储的是 class 文件的版本号。同样也是4个字节。第5个和第6个字节所代表的含义就是编译的副版本号minor_version,而第7个和第8个字节就是编译的主版本号major_version
    ●它们共同构成了class文件的格式版本号。譬如某个 class 文件的主版本号为M,副版本号为 m,那么这个Class 文件的格式版本号就确定为M.m。
    ●版本号和Java编译器的对应关系
    在这里插入图片描述
    ●Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1.
    不同版本的Java编译器编译的Class文件对应的版本是不一样的。目前,高版本的Java虛拟机可以执行低版本编译器生成的Class文件,但是低版本的Java虚拟机不能执行由高版本编译器生成的Class文件(即反之不行)。否则JVM会抛出
    java. lang. UnsupportedClassVersionError异常。( 向下兼容)I
    ●在实际应用中,由于开发环境和生产环境的不同,可能会导致该问题的发生。因此,需要我们在开发时,特别注意开发编译的JDK版本和生产环境中的JDK版本是否一致。
    ●虚拟机JDK版本为1.k (k >= 2)时,对应的class文件格式版本号的范围为45.0 --44+k.0 (含两端) 。

    常量池(class文件的基石)

    常量池是Class文件中内容最为丰富的区域之一。常量池对于Class文件中的字段和方法解析也有着至关重要的作用。常量池是整个class文件的基石。
    在这里插入图片描述
    ●在版本号之后,紧跟着的是常量池的数量,以及若干个常量池表项。
    ●常量池中常量的数量是不固定的,所以在常量池的入口需要放置一 项u2类型的无符号数,代表常量池容量计数值(constant_ pool count) 。与Java中语言习惯不一样的是, 这个容量计数是从1而不是0开始的。
    ●Class文件使用了一个前置的容量计数器( constant pool_ count) 加若干个连续的数据项(constant_ pool) 的形式来描述常量池内容。我们把这-系列连续常量池数据称为常量池集合
    常量池表项中,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放

    常量池计数器

    由于常量池的数量不固定,时长时短,所以需要放置两个字节来表示常量池容量计数值
    常量池容量计数值(u2类型) :从1开始,表示常量池中有多少项常量。即constant_ pool_ count=1表示常量池中有0个常量项
    通常我们写代码时都是从0开始的,但是这里的常量池却是从1开始,因为它把第0项常量空出来了。这是为了**满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”**的含义,这种情况可用索引值0来表示。

    常量池

    常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)
    它包含了class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。常量池中的每一项都具备相同的特征。第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte(标记字节、标签字节)。
    在这里插入图片描述
    全限定名com/test/Demo这个就是类的全限定名,仅仅是把包名的".“替换成”/",为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束。
    简单名称是指没有类型和参数修饰的方法或者字段名称,如:类的add()方法和num字段的简单名称分别是add和num。
    描述符的作用是用来描述字段的数据类型、方法的参数列表〈包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、 float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,详见下表:
    在这里插入图片描述
    补充说明:
    虚拟机在加载Class文件时才会进行动态链接,也就是说,Class 文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。当虚拟机运行时,需要从常量池中获得对应的符号引用,再在为加载过程中的解析阶段将其替换为直接引用,并翻译到具体的内存地址中。
    这里说明下符号引用和直接引用的区别与关联:
    符号引用: 符号引用以组符号来描述所引用的目标, 符号可以是任何形式的字面量, 只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
    直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能问接定位到目标的句柄。 直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。
    在这里插入图片描述
    在这里插入图片描述
    总结1:
    这14种表(或者常量项结构)的共同点是:表开始的第一位是一个u1类型的标志位(tag) ,代表当前这个常量项使用的是哪种表结构,即哪种常量类型。

    ●在常量池列表中, CONSTANT_ Utf8_ info常量项是一种使用改进过的UTF -8编码格 式来存储诸如文字字符串、类或者接口的全限定名、字段或者方法的简单名称以及描述符等常量字符申信息。

    ●这14种常量项结构还有一个特点是,其中13个常量项占用的字节固定,只有CONSTANT_ Utf8 info占用字节不固定,其大小由length决定。为什么呢?因为从常量池存放的内容可知,其存放的是字面量和符号引用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比如你定义一个类,类名可以取长取短,所以在没编译前,大小不固定,编译后,通过utf- 8编码,就可以知道其长度。

    总结2:
    , 常量池:可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型(后面的很多数据类型都会指向此处),也是占用Class文件空间最大的数据项目之一。

    ●常量池中为什么要包含这些内容
    Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态链接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态链接的内容,在虚拟机类加载过程时再进行详细讲解

    访问标识(access_flag、访问标志、访问标记)

    在常量池后,紧跟着访问标记。该标记使用两个字节表示,用于识别一些类或者接口层次的访问信息,包括:这个 Class是类还是接口;是否定义为 public类型,是否定义为 abstract类型;如果是类的话,是否被声明为 final等。各种访问标记如下所示:
    在这里插入图片描述
    ●类的访问权限通常为ACC_ 开头的常量。
    ●每一种类型的表示都是通过设置访问标记的32位中的特定位来实现的。比如若是public final的类, 则该标记为ACC_ PUBLIC| ACC_ F INAL。
    ●使用ACC_SUPER可以让类更准确地定位到父类的方法super .method(),现代编译器都会设置并且使用这个标记。

    补充说明:
    1.
    带有ACC_ INTERFACE标志的class文件表示的是接口而不是类,反之则表示的是类而不是接口。

    1. 如果一个class文件被设置了ACC_ INTERFACE 标志,那么同时也得设置ACC_ABSTRACT 标志。同时它不能再设置ACC_ FINAL、ACC_ SUPER或ACC_ ENUM 标志。
      2)如果没有设置ACC_ INTERFACE标志, 那么这个class文件可以具有上表中除ACC ANNOTATION外的其他所有标志。当然,ACC FINAL和ACC_ ABSTRACT这 类互斥的标志除外。这两个标志不得同时设置。
      2.
      ACC_ SUPER标志用于确定类或接口里面的invokespecial指令使用的是哪一种执行语 义。针对Java虚拟机指令集的编译器都应当设置这个标志。对于Java SE 8及后续版本来说,无论class文件中这个标志的实际值是什么,也不管class 文件的版本号是多少,Java虚拟机都认为每个class文件均设置了ACC_SUPER标志。

    1)ACC_ SUPER标志是为了向后兼容由旧Java编译器所编译的代码而设计的。目前的ACC_SUPER标志在由JDK 1.0.2之 前的编译器所生成的access_ flags中是没有确定含义的,如果设置了该标志,那么Oracle的Java虚拟机实现会将其忽略。

    3. ACC SYNTHETIC标志 意味着该类或接口是由编译器生成的,而不是由源代码生成的。

    4.注解类型必须设置ACC _ANNOTATION标志。如果设置了ACC ANNOTATION标志,那么 也必须设置ACC_INTERFACE标志。

    5. ACC_ENUM标志表明该类或其父类为枚举类型。

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

    在这里插入图片描述
    ●这三项数据来确定这个类的继承关系。

    ●类索引用于确定这个类的全限定名

    ●父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.object之外,所有的Java类都有父类,因此除了java. lang .0bject外,所有Java类的父类索引都不为0.

    ●接口索引集合就用来描述这个类实现了哪些接口,这些被实现的接口将按implements 语句(如果这个类本身是一个接口,则应当是extends 语句)后的接口顺序从左到右排列在接口索引集合中。

    1.this_ class (类索引)
    2字节无符号整数,指向常量池的索引。它提供了类的全限定名,如com/java1/Demo. this_ class的值必须是对常量池表中某项的一个有效索引值。 常量池 在这个索引处的成员必须为CONSTANT. _Class _info类型结构体, 该结构体表示这个class文件所定义的类或接口

    2.super_ class (父类索引)
    ●2字节无符号整数,指向常量池的索引。它提供了当前类的父类的全限定名。如果我们没有继承任何类,其默认继承的是java/lang/object类.同时,由于Java不支持多继承, 所以其父类只有一个。superclass指向的父类不能是final.

    3. interfaces
    ●指向常量池索引集合, 它提供了一个符号引用到所有己实现的接口
    ●由于一个类可以实现多个接口,因此需要以数组形式保存多个接口的索引,表示接口的每个索引也是一个指向常量池的CONSTANT_ Class ( 当然这里就必须是接口,而不是类)。
    3.1 interfaces_ count (接口计数器)
    interfaces_ count 项的值表示当前类或接口的直接超接口数量。
    3.2 interfaces [ ] 接口索引集合
    interfaces []中每个成员的值必须是对常量池表中某项的有效索引值,它的长度为interfaces_ count。 每 个成员interfaces[i]必须为CONSTANT_ Class_ info结构, 其中0 <= i < interfaces count。 在interfaces[]中, 各成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)一样,即interfaces[0]对 应的是源代码中最左边的接口。

    字段表的集合

    ●用于描述接口或举中声明的变量。字段(field) 包括类级变量以及实例级变量,但是不包括方法内部、代码块内部声明的局部变量
    ●字段叫什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。
    ●它指向常量池索引集合,它描述了每个字段的完整信息。比如字段的标识符、访问修饰符(public、 private或protected)、是类变量还是实例变量(static修饰符)、是否是常量(final修饰符)等。

    注意事项:
    ●字段表集合中不会列出父类或者实现的接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段。譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
    ●在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一 样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。

    1.fields_count(字段计数器)

    fields_count的值表示当前class文件fields表的成员个数。使用两个字节来表示。
    fields表中每个成员都是一个field_info结构,用于表示该类或接口所声明的所有类字段或者实例字段不包括方法内部声明的变量,也不包括从父类或父接口继承的那些字段。

    2 fields [] (字段表)

    ●fields表中的每个成员都必须是一个fields_ info结构的数据项,用于表示当前类或接口中某个字段的完整描述。
    ●一个字段的信息包括如下这些信息。这些信息中,各个修饰符都是布尔值,要么有,要么没有。

    作用域(public、 private、 protected修饰符)
    是实例变量还是类变量(static修饰符)
    可变性(final)
    并发可见性(volatile修饰符, 是否强制从主内存读写)
    可否序列化( transient修饰符
    字段数据类型(基本数据类型、对象、数组)
    字段名称

    字段表结构
    在这里插入图片描述

    2.1字段表访问标识

    一个字段可以被各种关键字去修饰,比如:作用域修饰符(public、private、protected). static修饰符、final修饰符、volatile修饰符等等。因此,其可像类的访问标志那样,使用一些标志来标记字段。字段的访问标志有
    在这里插入图片描述

    2.2字段名索引

    根据字段名索引的值,查询常量池中的指定索引项即可。

    2.3 描述符索引

    描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte, char , double,float ,int , long, short ,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示
    在这里插入图片描述

    2.4属性表集合

    一个字段还可能拥有一些属性,用于存储更多的额外信息。比如初始化值、一些注释信息等。属性个数存放在attribute_ count中,属性具体内容存放在attributes数组中。

    以常量属性为例。结构为:
    ConstantValue attribute{
    u2 attribute_ name_ index;
    u4 attribute length;|
    u2 constantvalue_ index;
    }

    说明:对于常量属性而言,attribute_ length值恒为2。

    方法表的集合

    methods:指向常量池索引集合,它完整描述了每个方法的签名

    ●在字节码文件中,每一个method_ info项都对应着一个类或者接口中的方法信息。比如方法的访问修饰符(public、private或protected),方法的返回值类型以及方法的参数信息等。

    ●如果这个方法不是抽象的或者不是native的, 那么字节码中会体现出来。

    ●一方面,methods表只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。另一方面,methods表有可能会出现由编译器自动添加的方法,最典型的便是编译器产生的方法信息(比如:类(接口 )初始化方法()和实例初始化方法().

    使用注意事项:
    在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名之中,因此Java语言里无法仅仅依靠返回值的不同来对一个已有方法进行重载。但在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个 方法就可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个class文件中。也就是说,尽管Java语法规范并不允许在一个类或 者接口中声明多个方法签名相同的方法,但是和Java语法规范相反, 字节码文件中却恰怡允许存放多个方法签名相同的方法,唯一的条件就是这些方法之间的返回值不能相同。

    1.methods_count(方法计数器)

    methods_count的值表示当前class文件methods表的成员个数。使用两个字节来表示。methods表中每个成员都是一个method_info结构。

    2.methods [(方法表)

    methods表中的每个成员都必须是一个method_info结构,用于表示当前类或接口中某个方法的完整描述。如果某个method_info结构的access_flags项既没有设置ACC_NATIVE 标志也没有设置ACC_ABSTRACT标志,那么该结构中也应包含实现这个方法所用的Java虚拟机指令。
    ●method_info结构可以表示类和接口中定义的所有方法,包括实例方法、类方法、实例初始化方法和类或接口初始化方法·方法表的结构实际跟字段表是一样的,方法表结构如下:
    在这里插入图片描述

    2.1方法表访问标志

    方法表也有访问标志,而且他们的标志有部分相同,部分则不同,
    在这里插入图片描述

    属性表集合

    方法表集合之后的属性表集合,指的是class文件所携带的辅助信息,比如该class文件的源文件的名称。以及任何带有RetentionPolicy.CLASS或者RetentionPolicy . RUNTIME的注解。这类信息通常被用于Java虚拟机的验证和运行,以及Java程序的调试,般无须深入了解。
    此外,字段表、方法表都可以有自己的属性表。用于描述某些场景专有的信息。
    属性表集合的限制没有那么严格,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。

    2.属性表attributes [ ]

    属性表的每个项的值必须是attribute_info结构。属性表的结构比较灵活。

    2.1属性的通用格式

    在这里插入图片描述

    2.2属性类型

    属性表实际上可以有很多类型,上面看到的Code属性只是其中一种,Java8里面定义了23种属性。
    其中:code属性
    在这里插入图片描述
    参考课程

    在这里插入图片描述
    在这里插入图片描述
    下一篇

    展开全文
  • Class文件结构

    千次阅读 2018-08-03 10:58:40
    Class文件结构 目录 Class类文件结构 无符号数 表 魔数 Class文件的版本号 常量池 访问标志(2字节) 类索引、父类索引和接口索引集合 字段表集合 方法表集合 属性表集合     Class类文件结构...

    Class文件结构

    目录

     


    Class类文件结构

    • Class文件是一组以8字节为基础单位的二进制流,
    • 各个数据项目严格按照顺序紧凑排列在class文件中,
    • 中间没有任何分隔符,这使得class文件中存储的内容几乎是全部程序运行的程序。

    Java虚拟机规范规定,Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表。

    无符号数

    属于基本数据类型,主要可以用来描述数字、索引符号、数量值或者按照UTF-8编码构成的字符串值,大小使用u1、u2、u4、u8分别表示1字节、2字节、4字节和8字节。

    是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有的表都习惯以“_info”结尾。表主要用于描述有层次关系的复合结构的数据,比如方法、字段。需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。具体的顺序定义如下:

    在class文件中,主要分为魔数、Class文件的版本号、常量池、访问标志、类索引(还包括父类索引和接口索引集合)、字段表集合、方法表集合、属性表集合。

    回到顶部

    魔数

    1. 每个Class文件的头4个字节称为魔数(Magic Number)
    2. 唯一作用是用于确定这个文件是否为一个能被虚拟机接受的Class文件。
    3. Class文件魔数的值为0xCAFEBABE。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。

    很多文件存储标准中都使用魔数来进行身份识别,譬如图片格式,如gif或jpeg等在文件头中都存有魔数。使用魔术而不是使用扩展名是基于安全性考虑的——扩展名可以随意被改变!!!

    回到顶部

    Class文件的版本号

    紧接着魔数的4个字节是Class文件版本号,版本号又分为:

    1. 次版本号(minor_version): 前2字节用于表示次版本号
    2. 主版本号(major_version): 后2字节用于表示主版本号。

    这个的版本号是随着jdk版本的不同而表示不同的版本范围的。Java的版本号是从45开始的。如果Class文件的版本号超过虚拟机版本,将被拒绝执行。

      0X0034(对应十进制的50):JDK1.8      
      0X0033(对应十进制的50):JDK1.7      
      0X0032(对应十进制的50):JDK1.6      
      0X0031(对应十进制的49):JDK1.5  
      0X0030(对应十进制的48):JDK1.4  
      0X002F(对应十进制的47):JDK1.3  
      0X002E(对应十进制的46):JDK1.2 
    ps:0X表示16进制

    回到顶部

    常量池

     紧接着魔数与版本号之后的是常量池入口.常量池简单理解为class文件的资源从库

    1. 是Class文件结构中与其它项目关联最多的数据类型
    2. 是占用Class文件空间最大的数据项目之一
    3. 是在文件中第一个出现的表类型数据项目

    由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。
    从1开始计数。Class文件结构中只有常量池的容量计数是从1开始的,第0项腾出来满足后面某些指向常量池的索引值的数据在特定情况下需要表达"不引用任何一个常量池项目"的意思,这种情况就可以把索引值置为0来表示(留给JVM自己用的)。但尽管constant_pool列表中没有索引值为0的入口,缺失的这一入口也被constant_pool_count计数在内。例如,当constant_pool中有14项,constant_poo_count的值为15。
    常量池之中主要存放两大类常量:

    1. 字面量: 比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等
    2. 符号引用: 属于编译原理方面的概念,包括了下面三类常量:
      •   类和接口的全限定名
      •   字段的名称和描述符
      •   方法的名称和描述符

    Java代码在进行Java编译的时候,并不像C和C++那样有"连接"这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法和字段的最终内存布局信息,因此这些字段和方法的符号引用不经过转换的话是无法被虚拟机使用的。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析并翻译到具体的内存地址之中。
    constant_pool_count:占2字节,本例为0x0016,转化为十进制为22,即说明常量池中有21个常量(只有常量池的计数是从1开始的,其它集合类型均从0开始),索引值为1~21。第0项常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示
    constant_pool:表类型数据集合,即常量池中每一项常量都是一个表,共有14种(JDK1.7前只有11种)结构各不相同的表结构数据。这14种表都有一个共同的特点,即均由一个u1类型的标志位开始,可以通过这个标志位来判断这个常量属于哪种常量类型,常量类型及其数据结构如下表所示:

    譬如utf-8类型的表结构数据

    譬如fieldref类型的表结构数据

    譬如class类型的表结构数据

    譬如nameandtype类型的表结构数据

    ps:什么是描述符?
    成员变量(包括静态成员变量和实例变量) 和方法都有各自的描述符。 
    对于字段而言,描述符用于描述字段的数据类型; 
    对于方法而言,描述符用于描述字段的数据类型、参数列表、返回值。
    在描述符中,基本数据类型用大写字母表示,对象类型用“L对象类型的全限定名”表示,数组用“[数组类型的全限定名”表示。 
    描述方法时,将参数根据上述规则放在()中,()右侧按照上述方法放置返回值。而且参数之间无需任何符号。

    回到顶部

    访问标志(2字节)

    常量池之后的数据结构是访问标志(access_flags),这个标志主要用于识别一些类或接口层次的访问信息,主要包括:

    • 是否final
    • 是否public,否则是private
    • 是否是接口
    • 是否可用invokespecial字节码指令
    • 是否是abstact
    • 是否是注解
    • 是否是枚举

     access_flags一共有16个标志位可以使用,当前只定义了其中8个(JDK1.5增加后面3种),没有使用到标志位一律为0。

    回到顶部

    类索引、父类索引和接口索引集合

    这三项数据主要用于确定这个类的继承关系。
    其中类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引(interface)集合是一组u2类型的数据。(多实现单继承)
    类索引(this_class),用于确定这个类的全限定名,占2字节
    父类索引(super_class),用于确定这个类父类的全限定名(Java语言不允许多重继承,故父类索引只有一个。除了java.lang.Object类之外所有类都有父类,故除了java.lang.Object类之外,所有类该字段值都不为0),占2字节
    接口索引计数器(interfaces_count),占2字节。如果该类没有实现任何接口,则该计数器值为0,并且后面的接口的索引集合将不占用任何字节,
    接口索引集合(interfaces),一组u2类型数据的集合。用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果该类本身为接口,则为extends语句)后的接口顺序从左至右排列在接口的索引集合中
    this_class、super_class与interfaces按顺序排列在访问标志之后,它们中保存的索引值均指向常量池中一个CONSTANT_Class_info类型的常量,通过这个常量中保存的索引值可以找到定义在CONSTANT_Utf8_info类型的常量中的全限定名字符串

    回到顶部

    字段表集合

    fields_count:字段表计数器,即字段表集合中的字段表数据个数,占2字节。本测试类其值为0x0001,即只有一个字段表数据,也就是测试类中只包含一个变量(不算方法内部变量)
    fields:字段表集合,一组字段表类型数据的集合。字段表用于描述接口或类中声明的变量,包括类级别(static)和实例级别变量,不包括在方法内部声明的变量
    在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是类级别变量还是实例级别变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示。

    类型

    名称

    数量

    说明

    u2

    access_flags

    1

    修饰符标记位

    u2

    name_index

    1

    代表字段的简单名称,占2字节,是一个对常量池的引用 

    u2

    descriptor_index

    1

    代表字段的类型,占2个字节,是一个对常量池的引用

    u2

    attributes_count

    1

    属性计数器

    attribute_info

    attributes

    attributes_count

    属性表集合

     

    字段表包含的固定数据项到descriptor_index结束,之后跟随一个属性表集合用于存储一些附加信息。 

    字段表集合中不会列出从父类或父接口中继承的字段,但是可能列出原本Java代码之中不存在的字段,如:内部类为了保持对外部类的访问性,自动添加指向外部类实例的字段。Java语言中字段是不能重载的,2个字段无论数据类型、修饰符是否相同,都不能使用相同的名称;但是对于字节码,只要字段描述符不同,字段重名就是合法的。

    回到顶部

    方法表集合

    methods_count:方法表计数器,即方法表集合中的方法表数据个数。占2字节,其值为0x0002,即测试类中有2个方法
    methods:方法表集合,一组方法表类型数据的集合。方法表结构和字段表结构一样。

    2个字节为属性计数器,其值为0x0001,说明这个方法的属性表集合中有一个属性(详细说明见后面“属性表集合”)
    属性名称为接下来2个字节0x0009,指向常量池中第9个常量,Code。
    接下来4个字节为0x0000002F,表示Code属性值的字节长度为47。
    接下来2个字节为0x0001,表示该方法的操作数栈的深度最大值为1。
    接下来2个字节依然为0x0001,表示该方法的局部变量占用空间为1。
    接下来4个字节为0x00000005,则紧接着的5个字节0x2AB70001B1为该方法编译后生成的字节码指令。
    接下来2个字节为0x0000,说明Code属性异常表集合为空。
    接下来2个字节为0x0002,说明Code属性带有2个属性,
    接下来2个字节0x000A即为Code属性第一个属性的属性名称,指向常量池中第10个常量:LineNumberTable。
    接下来4个字节为0x00000006,表示LineNumberTable属性值所占字节长度为6。
    接下来2个字节为0x0001,line_number_table中只有一个line_number_info表,start_pc为0x0000,line_number为0x0003,LineNumberTable属性结束。
    接下来2位0x000B为Code属性第二个属性的属性名,指向常量池中第11个常量:LocalVariableTable。该属性值所占的字节长度为0x0000000C=12。
    接下来2位为0x0001,说明local_variable_table中只有一个local_variable_info表,按照local_variable_info表结构,start_pc为0x0000,length为0x0005,name_index为0x000C,指向常量池中第12个常量:this,descriptor_index为0x000D,指向常量池中第13个常量:LTestClass;,index为0x0000。

    ps:

    如果子类没有重写父类的方法,方法表集合中就不会出现父类方法的信息;有可能会出现由编译器自动添加的方法(如:最典型的<init>,实例类构造器)在Java语言中,重载一个方法除了要求和原方法拥有相同的简单名称外,还要求必须拥有一个与原方法不同的特征签名(,由于特征签名不包含返回值,故Java语言中不能仅仅依靠返回值的不同对一个已有的方法重载;但是在Class文件格式中,特征签名即为方法描述符,只要是描述符不完全相同的2个方法也可以合法共存,即2个除了返回值不同之外完全相同的方法在Class文件中也可以合法共存。

    注意:Java代码的方法特征签名只包括方法名称、参数顺序、参数类型。  而字节码的特征签名还包括方法返回值和受异常表。

    回到顶部

    属性表集合

    起始2个字节为0x0001,说明有一个类属性。
    接下来2个字节为属性的名称,0x0010,指向常量池中第16个常量:SourceFile。
    接下来4个字节为0x00000002,说明属性体长度为2字节。
    最后2个字节为0x0011,指向常量池中第27个常量:TestClass.java,即这个Class文件的源码文件名为TestClass.java

    与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义了虚拟机实现必须能够识别的9项属性(预定义属性已经增加到21项):

    属性名称

    使用位置

    含义

    Code

    方法表

    Java代码编译成的字节码指令

    ConstantValue

    字段表

    final关键字定义的常量值

    Deprecated

    类文件、字段表、方法表

    被声明为deprecated的方法和字段

    Exceptions

    方法表

    方法抛出的异常

    InnerClasses

    类文件

    内部类列表

    LineNumberTale

    Code属性

    Java源码的行号与字节码指令的对应关系

    LocalVariableTable

    Code属性

    方法的局部变量描述(局部变量作用域)

    SourceFile

    类文件

    源文件名称

    Synthetic

    类文件、方法表、字段表

    标识方法或字段是由编译器自动生成的

    ps:在调试是可以通过SourceFile来关联相关的类。

     

    大总结的PS:

    1,全限定名:将类全名中的“.”替换为“/”,为了保证多个连续的全限定名之间不产生混淆,在最后加上“;”表示全限定名结束。例如:"com.test.Test"类的全限定名为"com/test/Test;"

    2,简单名称:没有类型和参数修饰的方法或字段名称。例如:"public void add(int a,int b){...}"该方法的简单名称为"add","int a = 123;"该字段的简单名称为"a"

    3,描述符:描述字段的数据类型、方法的参数列表(包括数量、类型和顺序)和返回值。根据描述符规则,基本数据类型和代表无返回值的void类型都用一个大写字符表示,而对象类型则用字符L加对象全限定名表示

    标识字符

    含义

    B

    基本类型byte

    C

    基本类型char

    D

    基本类型double

    F

    基本类型float

    I

    基本类型int

    J

    基本类型long

    S

    基本类型short

    Z

    基本类型boolean

    V

    特殊类型void

    L

    对象类型,如:Ljava/lang/Object;

    对于数组类型,每一维将使用一个前置的“[”字符来描述,如:"int[]"将被记录为"[I","String[][]"将被记录为"[[Ljava/lang/String;"

    用描述符描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组"()"之内,如:方法"String getAll(int id,String name)"的描述符为"(I,Ljava/lang/String;)Ljava/lang/String;"

    4,Slot,虚拟机为局部变量分配内存所使用的最小单位,长度不超过32位的数据类型占用1个Slot,64位的数据类型(long和double)占用2个Slot

     

    转自:https://www.cnblogs.com/wade-luffy/p/5929325.html

    展开全文
  • 解读Class文件结构

    千次阅读 2018-04-29 18:16:16
    一、Class文件结构 任何一个Class文件都对应着唯一 一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。 问题: 当出现超...

    语言无关性

    语言无关系的关键在 于 JVM字节码;Java虚拟机不与任何编译成class字节码的语言绑定,只要能够编译成有效的Class字节码都解析执行。

    语言无关性


    一、Class文件结构

    任何一个Class文件都对应着唯一 一个接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以通过类加载器直接生成)。


    问题: 当出现超过8位字节码来表示的数据项怎么办呢?

    会按照高位在前的方式分割成若干个8位字节进行存储;补充大端法小端法


    二 如何描述Class文件

    无符号 组合成类似与C结构的伪结构来描述Class文件。


    三、ClassFile Structure

    一个class 由单个ClassFile 结构组成。ClassFile由下面结构组成。

    ClassFile {
        u4             magic;        //识别Class文件格式,具体值为0xCAFEBABE; 即魔术
        u2             minor_version;//次版本号
        u2             major_version;//主版本号
        u2             constant_pool_count;//常量池容量计数
        cp_info        constant_pool[constant_pool_count-1];
    
        //它代表各种各样的字符串常量、类和接口名、字段名以及在ClassFile结构及其子结构中引用的其他常量。
        //每个常量池表条目的格式由它的第一个“标记”字节表示。
    
        u2             access_flags;     //访问标志
        u2             this_class;       //类索引
        u2             super_class;     //父类索引
        u2             interfaces_count;//接口索引数
        u2             interfaces[interfaces_count]; //接口索引集合
     //接口数组中的每个值必须是常量池中有效索引。
    
        u2             fields_count;     //字段数    
        field_info     fields[fields_count];
        u2             methods_count;   //方法数
        method_info    methods[methods_count]; 
    
        u2             attributes_count;  //属性数
        attribute_info attributes[attributes_count];
    }

    3.1 文件结构解析

    Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中中间没有添加任何分隔符


    特定描述
    8位字节为单位
    顺序有严格控制
    大小有规律,因为表是符合结构数据,大小不确定外,其他都是大小确定的。

    类型

    描述Class类文件结构的类型

    类型 描述
    无符号 u1u2u4u8来分别代表1个字节2个字节4个字节8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
    表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以_info结尾。表用于描述有层次关系的复合结构的数据

    3.2 字节码文件解析

    将下面一点代码使用JDK1.8编译成Class文件进行讲解


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

    通过命令javac将这个类编译成classs文件。notepad++装一个HEX-Editor 插件后,打开这个class文件。


    接下来就是解析这份字节码。


    3.2.1 魔术 cafe babe

      u4             magic; 

    每个Class文件的头四个字节称为魔数,它的唯一作用是用来确定该文件是否为一个能被虚拟机接受的Class文件。使用魔数而不使用文件扩展名是出于安全方面的考虑,因为文件扩展名可以很随意的被改动。


    3.2.2 版本号

     u2             minor_version;//次版本号
     u2             major_version;//主版本号

    minor_version:占2字节,次版本号,0x0000
    majro_version:占2字节,主版本号,0x0034, 转换过来就是52


    3.2.3 常量池(constant_pool_count 和 constant_pool)

    ClassFile {
    ...
        u2             constant_pool_count;
        cp_info        constant_pool[constant_pool_count-1];
    ...
    }
    

    constant_pool_count : 由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值 (计数从1开始,其他计数从0开始,因为0有其他作用)


    (一) 常量池数目:

    常量池容量(偏移地址:0x00000008)为十六进制数0x0016,即十进制的22,这就代表常量池中有21项常量,索引值范围为1~21


    (二)常量池内容:

    constant_pool : 主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。

    在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析翻译到具体的内存地址之中

    常量池的都是表结构,如上图展示,但是在常量池中的表的结构又是怎么样的呢?


    注意:所有常量池中的条码都具有下面通用结构:

    cp_info {
        u1 tag;
        u1 info[];
    }
    

    每个条码都有一个tag 去标志它
    常量池中的条目


    结合Class字节码来讲解常量池中各个类型的结构

    下面圈定了常量池中的内容,通过tag 定位到具体哪个类型,根据类型的结构进行逐一分析

    常量池中的内容


    按照常量池中类型,整理出21个记录

    序号 字节码 常量池类型 字符串
    1 0a 00 04 00 12 CONSTANT_Methodref_info
    2 09 00 03 00 13 CONSTANT_Fieldref
    3 07 00 14 CONSTANT_Class
    4 07 00 15 CONSTANT_Class
    5 01 00 01 6d CONSTANT_Utf8_info m
    6 01 00 01 49 CONSTANT_Utf8_info i
    7 01 00 06 36 69 6e 69 74 3e CONSTANT_Utf8_info
    8 01 00 03 28 29 56 CONSTANT_Utf8_info ()
    9 01 00 04 43 6f 64 65 CONSTANT_Utf8_info Code
    10 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 65 CONSTANT_Utf8_info LineNumberTable
    11 01 00 12 4c 6f 63 61 6c 56 72 69 61 62 6c 65 54 61 62 6c 65 CONSTANT_Utf8_info LocalVariableTable
    12 01 00 04 74 68 69 73 CONSTANT_Utf8_info this
    13 01 00 1f 4c 6f 72 67 2f 66 65 6e 69 78 73 6f 66 74 2f 63 6c 61 7a 7a 2f 54 65 73 74 43 6c 61 73 74 3b CONSTANT_Utf8_info Long/fenixsoft/clazz/TestClass;
    14 01 00 03 69 6e CONSTANT_Utf8_info inc
    15 01 00 03 28 29 49 CONSTANT_Utf8_info ()I
    16 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 CONSTANT_Utf8_info SourceFile
    17 01 00 0e 54 65 73 74 43 6c 61 73 73 2e 6a 61 76 61 CONSTANT_Utf8_info TestClass.java
    18 0c 00 07 00 08 CONSTANT_NameAndType
    19 0c 00 05 00 06 CONSTANT_NameAndType
    20 01 00 1d 6f 72 67 2f 66 65 6e 69 78 73 6f 66 74 2e 63 6c 61 7a 7a 2f 54 65 73 74 43 6c 61 73 73 CONSTANT_Utf8_info org/fenixsoft/clazz/TestClass
    21 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 CONSTANT_Utf8_info java/lang/Object

    constant_pool table 把上图看做常量池表; 可以理解为将所有


    同时也可利用javap 命令整理



    3.2.3.1 常量池分析

    (一) 根据tag值找类型:

    在常量池后面的第一个地址(偏移地址:0x0000000A),是 0x0A (十进制为10),tag=10; 找到Constant Pool tag 定位到了 CONSTANT_Methodref 这个类型。

    (二) 结构类型:

    Fields, methods, and interface methods 具有相似的结构

    CONSTANT_Fieldref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    
    CONSTANT_Methodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }
    
    CONSTANT_InterfaceMethodref_info {
        u1 tag;
        u2 class_index;
        u2 name_and_type_index;
    }

    分别对 CONSTANT_Methodref_infotag, class_index,name_and_type_index 进行介绍

    items 描述
    tag CONSTANT_Methodref_info结构的tag值为10
    class_index CONSTANT_Methodref_info 结构的class_index必须是一个类类型,而不是接口类型
    name_and_type_index name_and_type_index 的值必须是constant_pool表 中的一个有效索引;这索引值上的对应的常量池条目(The constant_pool entry) 也一定是CONSTANT_NameAndType_info 结构;这个条目也是具有字段或方法作为成员的类或接口类型

    【entry 条目;词条;账目;记录 】

    补充:假如CONSTANT_Methodref_info 结构的方法名称以'<' ('\u003c') 开始,那么这个方法一定是特定的 <init>; 代表一个实例初始方法,返回类型也一定是 void。

    16进制


    CONSTANT_NameAndType_info 结构类型

    CONSTANT_NameAndType_info {
        u1 tag;
        u2 name_index;
        u2 descriptor_index;
    }
    • tag: CONSTANT_NameAndType_infotag 值一定是 12

    • name_index : 它的值一定是constant_pool table(常量池表) 有效索引值。那个索引对应的constant_pool entry(常量池条目),一定是CONSTANT_Utf8_info 结构; 代表特定的 方法 名; 也可以表示字段或方法的有效限定名

    • descriptor_index: 它的值是constant_pool table(常量池表)的一个有效索引,这个constant_pool entry 将是一个CONSTANT_Utf8_info 为结构; 代表字段或者方法的描述符。


    结构: CONSTANT_Utf8_info

    CONSTANT_Utf8_info {
        u1 tag;
        u2 length;
        u1 bytes[length];
    }

    这个CONSTANT_Utf8_info 结构别用来表示常量字符串值。

    • tag:CONSTANT_Utf8_info 的tag值一定是 1
    • length: bytes数组的长度值;不是实际字符串的长度值。
    • bytes:字节数组包含了字符串的每个字节
      • 如果没有字节可是值为0
      • 也可能是因为没有字节分布于 [(byte)0xf0 , (byte)0xff] (0-255)

    length值说明了这个UTF-8编码的字符串长度是多少字节,它后面紧跟着的长度为length字节的连续数据是一个使用UTF-8缩略编码表示的字符串。UTF-8缩略编码与普通UTF-8编码的区别是:从'\u0001'到'\u007f'之间的字符(相当于1~127的ASCII码)的缩略编码使用一个字节表示,从'\u0080'到'\u07ff‘之间的所有字符的缩略编码用两个字节表示,从'\u0800'到'\uffff'之间的所有字符的缩略编码就按照普通UTF-8编码规则使用三个字节表示


    常量池中14中常量项结构总表

    可以通过查阅总表来获取各项信息


    没有出现在代码中的其他常量

    其中有一些常量似乎从来没有在代码中出现过,如“I”“V”“<init>”“LineNumberTable”
    “LocalVariableTable”等,这些看起来在代码任何一处都没有出现过的常量是哪里来的呢

    3.2.4 访问标记(access_flags)

    这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等

    TestClass是一个普通Java类,不是接口、枚举或者注解,被public关键字修饰但没有被声明为final和abstract,并且它使用了JDK 1.2之后的编译器进行编译,因此它的ACC_PUBLICACC_SUPER标志应当为,而ACC_FINALACC_INTERFACEACC_ABSTRACTACC_SYNTHETICACC_ANNOTATIONCC_ENUM这6个标志应当为,因此它的access_flags的值应为:0x0001|0x0020=0x0021


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

        类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系


    • 类索引用(this_class)于确定这个类的全限定名,
    • 父类索引(super_class)用于确定这个类的父类的全限定名。
    • 由于Java语言不允许多重继承,所以父类索引只有一个; 但是接口可以实现好几个,所以是个集合,
    • 除了java.lang.Object之外, 所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0

    结构描述:


    item desc
    this_class this_class 的值一定是常量池中的有效索引,这个索引对应的常量项是一个CONSTANT_Class_info ;这个表示由这个类文件定义的类或接口
    super_class 可能是0 ,或者是常量池中有效索引,这个索引对应的常量项是一个CONSTANT_Class_info;代表这个类文件对应类的父类;直接超类和它的任何超类都不能在其类文件结构的access flags项中设置ACC_FINAL 标志;加入这个值为0,那个这个文件一定是Object类
    interfaces_count 接口数量
    interfaces[] 数组中的每个值都必须是常量池中的有效索引,每个索引对应的常量项都是CONSTANT_Class_info 这样的结构。 顺序从左到右

    因为interfaces_count 数量为 0 ,所以后面的interfaces[] 就不占用地址。


    3.2.6 字段表集合

    结合案例,字段表的数量为 1个;让后对这个进行分析

    每个字段的结构都如下:

    field_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }

    一个类文件中没有两个字段可能具有相同的名称和描述。

    (一) 访问标识符( access_flags )如下:

    标志 描述
    ACC_PUBLIC 0x0001 public 访问修饰符
    ACC_PRIVATE 0x0002 private 访问修饰符
    ACC_PROTECTED 0x0004 protected 访问修饰符
    ACC_STATIC 0x0008 static
    ACC_FINAL 0x0010 final
    ACC_VOLATILE 0x0040 valatile
    ACC_TRANSIENT 0x0080 transient
    ACC_SYNTHETIC 0x1000 synthetic
    ACC_ENUM 0x4000 enum

    对访问标识符的解释
    ACC_PUBLICACC_PRIVATEACC_PROTECTED三个标志最多只能选择其一
    接口中的字段必须有ACC_PUBLICACC_STATICACC_FINAL标志
    ACC_ENUM标志指示该字段用于保存枚举类型的元素
    ACC_SYNTHETIC标志表明该字段是由编译器生成的,并没有出现在源代码中

    (二) name_index:

    对常量池的引用, 其值为常量池中的有效索引,代表着字段的简单名称。

    (三) descriptor_index:

    • 描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值
    • 根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值void类型都用一个大写V字符来表示,而对象类型则用字符L加对象的全限定名来表示


    • 对于数组类型,每一维度将使用一个前置的[字符来描述,如一个定义为java.lang.String[][]类型的二维数组,将被记录为:[[Ljava/lang/String,一个整型数组int[]将被记录为[I

    • 用描述符来描述方法时按照参数列表,返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号()之内。

      • 如方法 void inc()的描述符为()V
      • 方法 java.lang.String toString() 的描述符为()Ljava/lang/String
      • 方法 :
    int indexOf(char[]source, 
        int sourceOffset, 
        int sourceCount, 
        char[]target, 
        int targetOffset,  
        int targetCount, 
        int fromIndex)
    

    描述符为([CII[CIII)I


    字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。

    另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的



    3.2.7 方法集合

    结合案例,本次方法的数量为2

    Class文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式

    method_info {
        u2             access_flags;
        u2             name_index;
        u2             descriptor_index;
        u2             attributes_count;
        attribute_info attributes[attributes_count];
    }

    通过方法表结构,集合本文案例


    (一) access_flags:


    与属性进行对比:

    • 因为volatile关键字和transient关键字不能修饰方法,所以方法表的访问标志中没有了ACC_VOLATILE标志和ACC_TRANSIENT标志。
    • 与之相对的,synchronizednativestrictfpabstract关键字可以修饰方法,所以方法表的访问标志中增加了ACC_SYNCHRONIZEDACC_NATIVECC_STRICTFPACC_ABSTRACT标志。

    (二) name_index:

    (三) 描述符索引:

    描述符索引

    通过分析得出了public void init()


    方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为Code的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目


    与字段表集合相对应的,如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类构造器<clinit>方法和实例构造器<init>


    3.2.8 属性表(attribute_info)集合

    在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息;限制相对其他表相对宽松一些。

    对于每个属性,它的名称需要从常量池中引用一个CONSTANT_Utf8_info类型的常量来表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性去说明属性值所占用的位数即可。下面每个属性都具备的结构特征。

    attribute_info {
        u2 attribute_name_index;
        u4 attribute_length;
        u1 info[attribute_length];
    }

    在分析方法表的时候已经讲述了一个Code属性
    (一)Code属性描述

    Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口者抽象类中的方法就不存在Code属性

    下面的图中就是Code.


    Code的两个属性值:


    类型 名称 数量 描述
    u2 attribute_name_index 1 attribute_name_index是一项指向CONSTANT_Utf8_info型常量的索引,常量值固定为Code,它代表了该属性的属性名称
    u4 attribute_length 1 attribute_length指示了属性值的长度
    u2 max_stack 1 max_stack代表了操作数栈(Operand Stacks)深度的最大值。虚拟机运行的时候需要根据这个值来分配栈帧(StackFrame)中的操作栈深度
    u2 max_locals 1 max_locals代表了局部变量表所需的存储空间
    u4 code_length 1 code_length代表字节码长度;理论上一个方法的字节码不超过u4,但实际是u2,如果超过这个限制,javac会拒绝
    u1 code code_length code是用于存储字节码指令的一系列字节流
    u2 exception_table_length 1 异常表长度
    exception_info exception_table exception_table_length
    u2 attributes_count 1 属性数量
    attribute_info attributes attributes_count

    Code属性表中的code(字节码)

    2a b7 00 0a b1
    • 读入2a,查表得0x2A对应的指令为aload_0,这个指令的含义是将第0个Slot中为reference类型的本地变量推送到操作数栈顶
    • 读入b7,查表得0xB7对应的指令为invokespecial,这条指令的作用是以栈顶的reference类型的数据所指向的对象作为方法接收者,调用此对象的实例构造器方法、private方法或者它的父类的方法。这个方法有一个u2类型的参数说明具体调用哪一个方法,它指向常量池中的一个CONSTANT_Methodref_info类型常量,即此方法的方法符号引用。
    • 读入00 01,这是invokespecial的参数,查常量池得0x0001对应的常量为实例构造器<init>方法的符号引用。
    • 读入b1,查表得0xB1对应的指令为return,含义是返回此方法,并且返回值为void。这条指令执行后,当前方法结束。

    Java虚拟机执行字节码是基于栈的体系结构。但是与一般基于堆栈的零字节指令又不太一样,某些指令(如invokespecial)后面还会带有参数


    继续分析Code中的剩余两个属性:LineNumberTableLocalVariableTable

    LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它并不是运行时必需的属性,但默认会生成到Class文件之中,可以在Javac中分别使用-g:none-g:lines选项来取消或要求生成这项信息。

    如果选择不生成LineNumberTable属性,对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点

    line_number_table是一个数量为line_number_table_length、类型为line_number_info的集合,line_number_info表包括了start_pcline_number两个u2类型的数据项,前者是字节码行号,后者是Java源码行号

    归纳为下面的结构

    LineNumberTable_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 line_number_table_length;
        {   u2 start_pc;
            u2 line_number; 
        } line_number_table[line_number_table_length];
    }

    .LocalVariableTable属性结构如下

    LocalVariableTable_attribute {
        u2 attribute_name_index;
        u4 attribute_length;
        u2 local_variable_table_length;
        {   u2 start_pc;
            u2 length;
            u2 name_index;
            u2 descriptor_index;
            u2 index;
        } local_variable_table[local_variable_table_length];
    }
    • LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,
    • 它也不是运行时必需的属性,但默认会生成到Class文件之中,
    • 可以在Javac中分别使用-g:none或-g:vars选项来取消或要求生成这项信息。

    local_variable_info项

    item desc
    start_pclength 代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围
    name_indexdescriptor_index 指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符
    index 这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是64位类型时(double和long),它占用的Slot为indexindex+1两个


    另外一个方法(不做详细介绍了):


    使用javap打印信息

    args_size 为什么等于 一

    <init>()和inc(),都没有参数的,为什么args_size会为1?
    而且无论是在参数列表里还是方法体内,都没有定义任何局部变量,那Locals又为什么会等于1?

    在任何实例方法里面,都可以通过this关键字访问到此方法所属的对象。这个访问机制对Java程序的编写很重要,而它的实现却非常简单,仅仅是通过Javac编译器编译的时候把对this关键字的访问转变为对一个普通方法参数的访问,然后在虚拟机调用实例方法时自动传入此参数而已。因此在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个Slot位来存放对象实例的引用,方法参数值从1开始计算。这个处理只对实例方法有效,如果inc()声明为static,那Args_size就不会等于1而是等于0


    ClassFile 最后一个属性:


    到这个地方整个class文件就大致讲完了


    后记

    • 还有很多属性没有讲解;当分析到对应的表时查询官方文档即可
    • code 字节码指令还没有详细讲解;将在新的一篇中详细讲解。

    参考

    展开全文
  • 一-Class文件结构

    2020-11-08 18:59:18
    文件结构有几个部分? 字节码都有哪些?Integer x = 5 ;int y = 5;比较x==y都经过了哪些步骤 public class IntegerTest { public static void main(String[] args) { Integer i1 = 10; Integer i2 = 10; ...
  • Class文件结构

    2020-11-11 15:27:13
    Class文件结构 一、概述 1.字节码文件的跨平台性 2.java的前端编译器 3.透过字节码指令看代码细节 代码举例 二、虚拟机的基石:Class文件 三、Class文件结构 1.魔数:Class文件的标志 2.Class...
  • 【深入Java虚拟机】之二:Class文件结构

    万次阅读 多人点赞 2014-01-02 23:55:07
    不仅使用Java编译器可以把Java代码编译成存储字节码的Class文件,使用JRuby等其他语言的编译器也可以把程序代码编译成Class文件,虚拟机并不关心Class的来源是什么语言,只要它符合一定的结构,就可以在Java中运行。...
  • class文件结构

    2016-03-31 13:18:10
    因此class文件结构也是java跨平台很重要的一个基础。下面简单看看class文件结构: 以上是class文件的基本结构,整个class文件分Magic,Version,Constant_pool,Access_flag,This_class,S
  • JVM -- Class文件结构

    2020-10-28 17:37:32
    JVM – Class文件结构 Class 文件结构概述字节码文件的跨平台性Java 语言,跨平台的(write once, run anywhere) 当 Java 源代码成功编译成字节码后,如果想在不同的平台上面运行,则无须再次编译 这个优势不再那么...
  • 6.Class文件结构

    2019-11-19 11:31:38
    Class文件格式 魔数与Class文件版本 每个Class文件的头4个字节称为魔数(Magic Number),作用是确定这个文件是...
  • class文件结构详细解析

    多人点赞 2020-06-24 16:16:56
    之前一直好奇java文件经过编译后生成的class文件,到底存储了什么信息?是如何被jvm识别并执行的?我们可不可以通过外部力量修改class文件内容,以致可以修改程序的运行?于是我带着种种疑问,开始研究class文件。...
  • 下面我们详细了解Class文件:先对Class文件结构有个大体了解,并了解Class文件结构里的一些名称定义;而后再详细说明结构中每一项数据的含义,并用测试程序编译Class文件来分析验证Class文件结构
  • Class文件结构

    万次阅读 2020-08-04 14:06:53
    Class文件结构 Java技术能够一直保持非常好的向后兼容性,这点Class文件结构的稳定性功不可没。Java目前已经发展到JDK14,但是class文件结构的内容,绝大部分在JDK1.2时代就已经定义好了。虽然JDK1.2的内容比较古老...
  • Class文件结构

    千次阅读 2015-07-14 19:48:46
    1、概述 Class文件是一组以8位字节为...根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。 无符号数:属于基本数据类型,以u1、u2、u4、
  • JVM笔记5:Class文件结构

    万次阅读 多人点赞 2013-12-01 16:51:47
    Class文件是一组以8位字节为基础单位的二进制流,包含多个数据项目(数据项目的顺序,占用的字节数均由规范定义),各个数据项目严格按照顺序紧凑的排列在Class文件中,不包含任何分隔符,使得整个Class文件中存储的...
  • 最通俗易懂的Class文件结构(下)

    万次阅读 多人点赞 2019-11-26 09:20:17
    上一篇文章分享了Class文件的主要构成,同时也详细分析了魔数、次版本号、主版本号、常量池集合、访问标志的构造,接下来继续详细分析类索引、父类索引、接口索引集合、字段表集合、方法表集合和属性表集合。
  • Class文件结构

    千次阅读 2016-05-29 16:31:34
    接下来,就应该看看Java的类加载机制,看看虚拟机是如何将Java代码文件编译后的class文件加载到Java内存中的。 Java是一门平台无关语言,只要有Java的运行环境,编写的代码可以运行在各种机器上,做到了“一次编码、...
  • Java Class文件结构

    2015-05-07 14:48:12
    java Class文件结构  java虚拟机规范第二版,Java虚拟机规范第三版(java se 1.7版本),发现java虚拟机规范中java class的文件结构部分并没有太大的变化。 java语言是跨平台的,所谓一次编写,到处运行...
  • Class文件结构分析

    万次阅读 2019-04-28 17:18:03
    Class文件结构分析 1. Class文件结构概览图 2. 每一项数据说明 类型 名称 数量 说明 u4 magic 1 魔数:确定一个文件是否是Class文件 u2 minor_version 1 Class文件的次版本号 u2 major_version 1 ...
  • 深入理解JVM(七)——Class文件结构

    万次阅读 2016-04-25 20:20:27
    什么是JVM的“无关性”?Java具有平台无关性,也就是任何操作系统都能运行Java代码。之所以能实现这一点,是因为Java运行在虚拟机之上,不同的操作系统都拥有各自的Java虚拟机,因此Java能实现“一次编写,处处运行...

空空如也

1 2 3 4 5 ... 20
收藏数 929,185
精华内容 371,674
关键字:

class文件结构