Class文件结构&字节码指令

class文件结构

Class文件存储的内容称为字节码(ByteCode),包含了JVM指令集和符号表以及若干其余辅助信息。html

class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间没有添加任何分隔符,整个Class文件中存储的内容几乎所有是程序运行的必要的数据,没有空隙存在。java

当遇到8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。web

Class文件中有两种数据类型,分别是无符号数和表。shell

无符号数

无符号数属于基本的数据类型,以u一、u二、u四、u8来表示一个字节、两个字节...的无符号数;无符号数用来描述数字、索引引用、数量值或UTF-8编码构成的字符串值。bootstrap

表是由多个无符号数或其余表做为数据项构成的复合数据类型,通常以"_info"结尾,用来描述class文件的数据结构。数组

特色:节省存储空间,提升处理性能bash

ClassFile { 
    u4 magic; 
    u2 minor_version; 
    u2 major_version; 
    u2 constant_pool_count; 
    cp_info constant_pool[constant_pool_count-1]; 
    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]; 
}
复制代码
  • 魔数
  • Class文件版本
  • 常量池
  • 访问标志
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

u2表示无符号数2个字节 u4表示无符号数4个字节数据结构

Class文件设计理念和意义

1. 魔数magic

魔数的惟一做用是肯定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。oracle

证实magic做用

建立一个class文件 magic.class ,内容是magic test,直接运行java magic操做:jvm

84407@FantJ MINGW64 ~/Desktop
$ java magictest
▒▒▒▒: ▒▒▒▒▒▒▒▒ magictest ʱ▒▒▒▒ LinkageError
        java.lang.ClassFormatError: Incompatible magic value 1886741100 in class file magictest

复制代码

报错意思是:magic矛盾,而后给了个magic value的十进制数,那么能够识别的magic十进制应该是多少呢。

应该是 3405691582

那么,而后我用javac编译的正常java文件生成class文件,用binary viewer 查看:

minor_version、major_version

魔数日后后面四位:表示字节码版本,分别表示Class文件的副、主版本。当今用的最广的几个版本: jdk1.8:52 jdk1.7:51 jdk1.6:50

对应版本号是52,是jdk1.8

版本向下兼容

2. constant_pool_count

常量池计数器,值等于constant_pool表中的成员数加1,占用两个字节

3. constant_pool[]常量池

Java虚拟机指令执行时依赖常量池(constant_pool)表中的符号信息。

全部的常量池项都具备以下通用格式:

cp_info {
    u1 tag; 
    u1 info[]; 
}
复制代码

info[]项的内容tag由的类型所决定。tag有效的类型和对应的取值在下表列出

常量类型
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18
3.1 CONSTANT_Class_info结构

表示类或接口

CONSTANT_Class_info {
    u1 tag; 
    u2 name_index;
}
复制代码

name_index必须是对常量池的一个有效索引

3.2 CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info结构

字段:

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; 
}
复制代码

class_index必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。

CONSTANT_Methodref_info结构的class_index项的类型必须是类(不能是接口)。CONSTANT_InterfaceMethodref_info结构的class_index项的类型必须是接口(不能是类)。CONSTANT_Fieldref_info结构的class_index项的类型既能够是类也能够是接口。

name_and_type_index必须是对常量池的有效索引,表示当前字段或方法的名字和描述符。 在一个CONSTANT_Fieldref_info结构中,给定的描述符必须是字段描述符。而CONSTANT_Methodref_infoCONSTANT_InterfaceMethodref_info中给定的描述符必须是方法描述符。

3.3 CONSTANT_String_info结构

用来表示String的结构

CONSTANT_String_info {
    u1 tag;
    u2 string_index;
}
复制代码

string_index必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info 结构,表示一组Unicode码点序列,这组Unicode码点序列最终会被初始化为一个String对象。

3.4CONSTANT_Integer_info和CONSTANT_Float_info结构

表示4字节(int和float)的数值常量:

CONSTANT_Integer_info {
    u1 tag; 
    u4 bytes; 
} 
CONSTANT_Float_info { 
    u1 tag; 
    u4 bytes;
}
复制代码
3.5CONSTANT_Long_info和CONSTANT_Double_info结构

表示8字节(long和double)的数值常量

CONSTANT_Long_info {
    u1 tag; 
    u4 high_bytes; 
    u4 low_bytes; 
} 

CONSTANT_Double_info { 
    u1 tag; 
    u4 high_bytes; 
    u4 low_bytes; 
}
复制代码
3.6 CONSTANT_NameAndType_info结构

表示字段或方法,可是和前面介绍的3个结构不一样,CONSTANT_NameAndType_info结构没有标识出它所属的类或接口

CONSTANT_NameAndType_info { 
    u1 tag; 
    u2 name_index; 
    u2 descriptor_index;
}
复制代码

name_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名(Unqualified Name)。

descriptor_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info结构,这个结构表示一个有效的字段描述符或方法描述符。

3.7 CONSTANT_Utf8_info结构

用于表示字符串常量的值

CONSTANT_Utf8_info {
    u1 tag; 
    u2 length; 
    u1 bytes[length]; 
}
复制代码

CONSTANT_Utf8_info结构中的内容是以length属性肯定长度的

3.8 CONSTANT_MethodHandle_info结构

表示方法句柄

CONSTANT_MethodHandle_info {
    u1 tag;
    u1 reference_kind;
    u2 reference_index;
}
复制代码

reference_kind项的值必须在1至9之间(包括1和9),它决定了方法句柄的类型。

  1. 若是reference_kind项的值为1(REF_getField)、2(REF_getStatic)、3(REF_putField)或4(REF_putStatic),那么常量池在reference_index索引处的项必须是CONSTANT_Fieldref_info结构,表示由一个字段建立的方法句柄。
  2. 若是reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或8(REF_newInvokeSpecial),那么常量池在reference_index索引处的项必须是CONSTANT_Methodref_info结构,表示由类的方法或构造函数建立的方法句柄。
  3. 若是reference_kind项的值是9(REF_invokeInterface),那么常量池在reference_index索引处的项必须是CONSTANT_InterfaceMethodref_info结构,表示由接口方法建立的方法句柄。
  4. 若是reference_kind项的值是5(REF_invokeVirtual)、6(REF_invokeStatic)、7(REF_invokeSpecial)或9(REF_invokeInterface),那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()。
  5. 若是reference_kind项的值是8(REF_newInvokeSpecial),那么方法句柄对应的方法必须为实例初始化()方法。
3.9 CONSTANT_MethodType_info结构

表示方法类型

CONSTANT_MethodType_info { 
    u1 tag; 
    u2 descriptor_index; 
}
复制代码
3.10 CONSTANT_InvokeDynamic_info结构

表示invokedynamic指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、参数和请求返回类型、以及能够选择性的附加被称为静态参数(Static Arguments)的常量序列。

CONSTANT_InvokeDynamic_info { 
    u1 tag; 
    u2 bootstrap_method_attr_index; 
    u2 name_and_type_index; 
}
复制代码

bootstrap_method_attr_index项的值必须是对当前Class文件中引导方法表的bootstrap_methods[]数组的有效索引。

name_and_type_index项的值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符。

4. access_flags:访问标志

访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。access_flags的取值范围和相应含义见下表。

标记名 含义
ACC_PUBLIC 0x0001 能够被包的类外访问。
ACC_FINAL 0x0010 不容许有子类。
ACC_SUPER 0x0020 当用到invokespecial指令时,须要特殊处理的父类方法。
ACC_INTERFACE 0x0200 标识定义的是接口而不是类。
ACC_ABSTRACT 0x0400 不能被实例化。
ACC_SYNTHETIC 0x1000 标识并不是Java源码生成的代码。
ACC_ANNOTATION 0x2000 标识注解类型
ACC_ENUM 0x4000 标识枚举类型

5. this_class:类索引

this_class的值必须是对constant_pool表中项目的一个有效索引值。

是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。

6. super_class:父类索引

表示这个Class文件所定义的类的直接父类,若是Class文件的super_class的值为0,那这个Class文件只多是定义的是java.lang.Object类,只有它是惟一没有父类的类

是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。

7. interfaces_count:接口计数器

表示有这个类有几个接口。

8. interfaces[]:接口表

成员所表示的接口顺序和对应的源代码中给定的接口顺序(从左至右)同样,即interfaces[0]对应的是源代码中最左边的接口。

是一个对constant_pool表中项目的一个有效索引值,表示指向常量池的第几个位置。

表示当前类或接口的直接父接口数量

9. fields_count:字段计数器

表示当前Class文件fields[]数组的成员个数

10. fields[]:字段表

每一个成员都必须是一个fields_info结构的数据项,描述当前类或接口声明的全部字段,但不包括从父类或父接口继承的部分。

用于表示当前类或接口中某个字段的完整描述

field_info {
    u2 access_flags; 
    u2 name_index;      //对常量池的一个有效索引
    u2 descriptor_index;     //对常量池的一个有效索引
    u2 attributes_count;     //当前字段的附加属性的数量
    attribute_info attributes[attributes_count];
}
复制代码

access_flags项的值是用于定义字段被访问权限和基础属性的掩码标志。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 volatile,表示字段是易变的。
ACC_TRANSIENT 0x0080 transient,表示字段不会被序列化。
ACC_SYNTHETIC 0x1000 表示字段由编译器自动产生。
ACC_ENUM 0x4000 enum,表示字段为枚举类型。

attributes表的每个成员的值必须是attribute结构,一个字段能够有任意个关联属性。

11. methods_count:方法计数器

methods_count的值表示当前Class文件methods[]数组的成员个数,Methods[]数组中每一项都是一个method_info结构的数据项。

12. methods[]:方法表

method_info结构能够表示类和接口中定义的全部方法,包括实例方法、类方法、实例初始化方法方法和类或接口初始化方法方法。methods[]数组只描述当前类或接口中声明的方法,不包括从父类或父接口继承的方法。

methods[]数组中的每一个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。

method_info { 
    u2 access_flags; 
    u2 name_index; 
    u2 descriptor_index; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count]; 
}
复制代码

access_flags项的值是用于定义当前方法的访问权限和基本属性的掩码标志,access_flags的取值范围和相应含义见下表所示。

标记名 说明
ACC_PUBLIC 0x0001 public,方法能够从包外访问
ACC_PRIVATE 0x0002 private,方法只能本类中访问
ACC_PROTECTED 0x0004 protected,方法在自身和子类能够访问
ACC_STATIC 0x0008 static,静态方法
ACC_FINAL 0x0010 final,方法不能被重写(覆盖)
ACC_SYNCHRONIZED 0x0020 synchronized,方法由管程同步
ACC_BRIDGE 0x0040 bridge,方法由编译器产生
ACC_VARARGS 0x0080 表示方法带有变长参数
ACC_NATIVE 0x0100 native,方法引用非java语言的本地方法
ACC_ABSTRACT 0x0400 abstract,方法没有具体实现
ACC_STRICT 0x0800 strictfp,方法使用FP-strict浮点格式
ACC_SYNTHETIC 0x1000 方法在源文件中不出现,由编译器产生

name_indexdescriptor_index 两属性是对常量池的一个有效索引 attributes_count的项的值表示这个方法的附加属性的数量。 attributes 表的每个成员的值必须是attribute结构,一个方法能够有任意个与之相关的属性。

13. attributes_count:属性计数器

attributes表中每一项都是一个attribute_info结构的数据项。

attributes_count的值表示当前Class文件attributes表的成员个数。

14. attributes[]:属性表

attributes表的每一个项的值必须是attribute_info结构,在Class文件格式中的ClassFile结构、field_info结构,method_info结构和Code_attribute结构都有使用,全部属性的通用格式以下:

attribute_info {
    u2 attribute_name_index; 
    u4 attribute_length; 
    u1 info[attribute_length];
}
复制代码

attribute_name_index必须是对当前Class文件的常量池的有效16位无符号索引。表示当前属性的名字。

attribute_length项的值给出了跟随其后的字节的长度,这个长度不包括attribute_name_indexattribute_name_index项的6个字节。

14.1 ConstantValue属性

ConstantValue属性是定长属性,位于field_info结构的属性表中。若是该字段为静态类型(即field_info结构的access_flags项设置了ACC_STATIC标志),则说明这个field_info结构表示的常量字段值将被分配为它的ConstantValue属性表示的值,这个过程也是类或接口申明的常量字段(Constant Field)初始化的一部分。这个过程发生在引用类或接口的类初始化方法执行以前。

ConstantValue_attribute { 
    u2 attribute_name_index; 
    u4 attribute_length; 
    u2 constantvalue_index; 
}
复制代码

attribute_name_index项的值,必须是一个对常量池的有效索引。 attribute_length项的值固定为2。 constantvalue_index项的值,必须是一个对常量池的有效索引。

14.2 Code属性

Code属性是一个变长属性,位于method_info结构的属性表。一个Code属性只为惟一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。全部Java虚拟机实现都必须可以识别Code属性。若是方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性,其它状况下,method_info有必须有明确的Code属性。

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length; 
    u2 max_stack;
    u2 max_locals;
    u4 code_length; 
    u1 code[code_length]; 
    u2 exception_table_length; 
    {   u2 start_pc;
        u2 end_pc; 
        u2 handler_pc; 
        u2 catch_type; 
    } exception_table[exception_table_length]; 
    u2 attributes_count; 
    attribute_info attributes[attributes_count];
}
复制代码

attribute_name_index项的值必须是对常量池的有效索引 attribute_length项的值表示当前属性的长度,不包括开始的6个字节。 max_stack项的值给出了当前方法的操做数栈在运行执行的任什么时候间点的最大深度。 max_locals项的值给出了分配在当前方法引用的局部变量表中的局部变量个数,包括调用此方法时用于传递参数的局部变量。long和double型的局部变量的最大索引是max_locals-2,其它类型的局部变量的最大索引是max_locals-1. code_length项给出了当前方法的code[]数组的字节数,code_length的值必须大于0,即code[]数组不能为空。 code[]数组给出了实现当前方法的Java虚拟机字节码。 exception_table_length项的值给出了exception_table[]数组的成员个数量。 exception_table[]数组的每一个成员表示code[]数组中的一个异常处理器(Exception Handler)。exception_table[]数组中,异常处理器顺序是有意义的(不能随意更改)。 start_pcend_pc两项的值代表了异常处理器在code[]数组中的有效范围。 handler_pc项表示一个异常处理器的起点 若是catch_type项的值不为0,那么它必须是对常量池的一个有效索引 attributes_count项的值给出了Code属性中attributes表的成员个数。 属性表的每一个成员的值必须是attribute结构。一个Code属性能够有任意数量的可选属性与之关联。

14.3 StackMapTable属性

StackMapTable属性是一个变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的类型阶段被使用。

StackMapTable_attribute { 
    u2 attribute_name_index;
    u4 attribute_length; 
    u2 number_of_entries; 
    stack_map_frame entries[number_of_entries];
}
复制代码

attribute_name_index项的值必须是对常量池的有效索引 attribute_length项的值表示当前属性的长度,不包括开始的6个字节。 number_of_entries项的值给出了entries表中的成员数量。Entries表的每一个成员是都是一个stack_map_frame结构的项。 entries表给出了当前方法所需的stack_map_frame结构。

...更多的属性就不在这一一贴了,太多了,须要的时候查官方文档便可:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.4

字节码指令

java虚拟机指令由一个字节长度的,表明某种特定操做含义的数字(称之为操做码),以及随后的表明此操做所需参数的操做数而构成。

操做码的长度为1个字节,因此最大只有256条

常量入栈指令

局部变量值转载到栈中指令

将栈顶值保存到局部变量中指令

wide指令

通用(无类型)栈操做指令

类型转换指令

整数运算

浮点运算

逻辑运算——移位运算

逻辑运算——按位布尔运算

控制流指令——条件跳转指令

控制流指令——比较指令

控制流指令——无条件跳转指令

控制流指令——表跳转指令

控制流指令——异常和finally

对象操做指令

数组操做指令

方法调用指令

方法返回指令

线程同步指令

指令参考:https://blog.csdn.net/web_code/article/details/12164733

一个简单的demo分析

Test.java

public class Test {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = a+b;
        System.out.println(c);
    }
}
复制代码

javap -v Test.class

 #2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
 #3 = Methodref #26.#27 // java/io/PrintStream.println:(I)V
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        10    //把10扩展成int入栈
         2: istore_1      //将栈顶int类型值保存到局部变量1中
         3: bipush        20     //把20扩展成int入栈
         5: istore_2     //将栈顶int类型值保存到局部变量2中
         6: iload_1      //从局部变量1中装载int类型值入栈  
         7: iload_2     //从局部变量2中装载int类型值入栈  
         8: iadd       // 将栈顶两int类型数相加,结果入栈。
         9: istore_3     //将栈顶int类型值保存到局部变量3中
        10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;获取静态字段的值。#2表示常量池的索引
        13: iload_3
        14: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V 运行时方法绑定调用方法。
        17: return      //void函数返回。
复制代码
相关文章
相关标签/搜索