javaClass类文件结构,类加载过程以及内存分配

知识要点:

Java虚拟机简介

Class类文件结构

Class类文件结构详解

Java虚拟机简介

Java虚拟机(JVM)是运行Java程序的抽象计算机,它是一种计算机设备的规范,可以采用不同的方式进行实现。Java程序通过运行在JVM中从而实现跨平台特性。
Java虚拟机不和包括Java在内的任何语言绑定,它只和Class文件这种特定的二进制文件格式关联,Class文件中包含了虚拟机指令集和符号表以及若干其他辅助信息。基于安全方面的考虑,Java虚拟机规范要求在Class文件中使用许多强制性的语法和结构化约束,但任一门功能性语言都可以表示为一个能被Java虚拟机所接受的有效Class文件。
问题:是不是只有Java编译器才能完成Java到class字节码文件的编译过程?


虚拟机产品

  • Sun HotSpot
  • BEA JRocket
  • IBM J9
  • Microsoft JVM
  • Google Android Dalvik

Class类文件结构

Class文件是一组以8位字节为基础单位的二进制流,各项数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,如果是超过8位字节以上空间的数据项,则会按照高位在前的方式(Big-Endian)分割成若干个8位字节进行存储。


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

Class类文件组成

  • Class文件中包含:
  • Java虚拟机指令集
  • 符号表
  • 若干其他辅助信息
    Class文件中只有两种数据类型
  • 无符号数

  • 无符号数
    无符号数属于基本的数据类型,可用来描述数字,索引引用,数量值或者按照UTF-8编码构成的字符串值。无符号数据类型包含如下4种:
  • u1:1个字节
  • u2:2个字节
  • u4:4个字节
  • u8:8个字节

    表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于描述由层次关系的复合结构的数据,整个Class文件本质上就是一张表

Class文件结构详解

常量池
Java代码在编译后,Class文件并不会保存各个方法、字段的最终内存布局,因为在编译时,Java类并不知道引用类的实际内存地址,因此只能使用符号引用来代替,而这些字段、方法引用不经过运行期转行就无法得到真正的内存地址入口。
当虚拟机运行式,需要从常量池获得对应的符号引用,而后再类创建或运行时解析成具体的内存地址,比如:在类加载器加载Test类时,此时可以通过虚拟机获取Test类的实际内存地址,因此便可以将符号ai.yunxi.Test替换为该类的实际内存地址。
符号引用包含三类常量:

  • 类和接口的全限定名, 如org.springframework.web.servlet.DispatherServlet
  • 字段的名称和描述符
  • 方法的名称和描述符
    常量池结构


    访问标志
    紧接着常量池之后的两个字节代表访问标志(access_flags),用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口、是否为public类型、是否为abstract类型、类是否声明为final等。标志位及其含义如下表:
ACC_FINAL 0X0010 声明为final,只有类可以设置
ACC_SUPER 0X0020 使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后编译出来的类都为真
ACC_INTERFACE 0X0200 接口
ACC_ABSTRACT 0X0400 abstract类型,对于接口或者抽象类来说,此标志值为真,其他类为假
ACC_SYNTHETIC 0X1000 这个类并非由用户代码产生
ACC_ANNOTATION 0X2000 注解
ACC_ENUM 0X4000 枚举

类父类与接口集合
访问标志之后顺序排列类索引(this)、父类索引(super)、接口索引集合(interfaces)。Class文件由这三项来确定这个类的集成关系。类索引和父类索引都是u2类型的数据。接口索引集合入口第一项是u2类型的接口计数器(interfaces_count)表示索引表的容量(即实现了几个接口)。如果该类没用实现任何接口,则计数器值为0,后面的接口索引表不再占用任何字节。
字段表集合
接口索引集合后边的是字段计数器:用于标识有多少个字段。字段计数器之后接着就是字段表集合。字段表(field_info)用于描述接口或者类中声明的变量。字段包括类级变量以及实例级变量。可以包括的信息有:

  • 字段的作用域(public、private、protected修饰符)
  • 实例变量还是类变量(static修饰符)
  • 可变性(final)
  • 并发可见性(volatile)
  • 可否被序列化(transient)
  • 字段数据类型(基本类型,对象,数组)
  • 字段名称
    字段表结构
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attribute_count 1
attribute_info attributes attribute_count

字段访问标志

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 0X0100 字段是否由编译器自动产生的
ACC_ENUM 0X0400 字段是否enum

全限定名、简单名称及描述符

  • 全限定名
  • 简单名称:没有类型和参数修饰的方法或者字段名称,如:inc()和int m简单名称就是:inc、m
  • 描述符:用来描述字段的数据类型、方法的参数列表(数量、类型及顺序)和返回值
    方法表集合
    通过访问标志、名称索引、描述符索引可清楚的表达方法的定义,属性表是Class文件格式中最具扩展性的一种数据项目。
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 方法是否由编译器产生的桥接方法
ACC_VARARGS 0X0080 方法是否接受不定参数
ACC_NATIVE 0X0100 方法是否为native
ACC_ABSTRACT 0X0400 方法是否为abstract
ACC_STRICTFP 0X0800 方法是否为strictfp
ACC_SYNTHETIC 0X1000 防范是否由编译器自动产生

属性表
Class文件、字段表、方法表、属性表都可以携带自己的属性表集合,用于描述某些场景专有的信息。属性表集合的限制稍微宽松,不再要求各个属性表具有严格顺序,只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息。

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

属性表结构

u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

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

u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stack 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_table_length
u2 attribute_count
attribute_info attributes attribute_count

attribute_name_index:指向CONSTANT_Utf8_info类型常量的值固定为“Code”

  • attribute_length:属性值的总长度
  • max_stack:操作数栈(Operand Stacks)深度的最大值
  • max_locals:局部变量所需的存储空间(单位:Slot)
  • code_length:和code是用来存储Java源程序编译后产生的字节码指令
    异常属性表
start_pc u2 1
end_pc u2 1
handle_pc u2 1
catch_type u2 1

Exceptions属性表
Exceptions属性是在方法表中与Code属性平级的一项属性。Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Exceptions), 也就是方法描述时在throws关键字后面列举的异常

attribute_name_index u2 1
attribute_length u4 1
number_of_exceptions u2 1
exception_index_table u2 number_of_exceptions

LineNumberTable属性表
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。可以在编译的时候分别使用-g:none和-g:lines选项来取消或者要求生成这项信息。

attribute_name_index u2 1
attribute_length u4 1
line_number_table_length u2 1
line_number_table line_number_info line_number_table_length

LocalVariableTable属性表
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系。

attribute_name_index u2 1
attribute_length u4 1
local_varible_table_length u2 1
local_variable_table local_variable_info local_varible_table_length

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

start_pc u2 1
length u2 1
name_index u2 1
descriptor_index u2 1
index u2 1

SourceFile
SourceFile属性用于记录生成这个Class文件的源码文件名称。sourcefile_index数据项是指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件的文件名。

attribute_name_index u2 1
attribute_length u4 1
sourcefile_index u2 1

ConstantValue
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的常量(类变量)才可以使用这项属性。目前Sun Javac编译器的选择是:如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或者java.lang.String的话,就生成ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在方法中进行初始化。

attribute_name_index u2 1
attribute_length u4 1
constantvalue_index u2 1

InnerClasses
InnerClasses属性用于记录内部类与宿主类之间的关联

attribute_name_index u2 1
attribute_length u4 1
number_of_classes u2 1
inner_classes inner_classes_info number_of_classes

number_of_classes代表需要记录多少个内部类信息。
inner_classes_info

inner_class_info_index u2 1
outer_class_info_index u2 1
inner_name_index u2 1
inner_class_access_flags u2 1

inner_class_info_index和outer_class_info_index都是指向常量池中CONSTANT_Class_info型常量的索引,分别代表了内部类和宿主类的符号引用。

  • inner_name_index代表内部类的名称
  • inner_class_access_flags是内部类的访问标志
    Deprecated和Synthetic
    Deprecated和Synthetic都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念。Deprecated代表已经不再推荐使用。Synthetic代表字段或者方法并不是有Java源码直接产生的,而是由编译器自行添加的。
attribute_name_index u2 1
attribute_length u2 1

StackMapTable
StackMapTable属性在JDK1.6发布后增加到了Class文件规范中,它是一个复杂的变长属性,位于Code属性的属性表中。会在虚拟机类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。一个方法的Code属性最多只能有一个StackMapTable属性。

attribute_name_index u2 1
attribute_length u4 1
number_of_entries u2 1
stack_map_frame stack_map_frame entries

注:近期和大家分享有关jvm相关的技术点,欢迎大家讨论学习!

你可能感兴趣的:(javaClass类文件结构,类加载过程以及内存分配)