《深入理解Java虚拟机》之虚拟机执行子系统

该文章为《深入理解Java虚拟机》的阅读笔记

第三部分 - 虚拟机执行子系统

第6章 类文件结构

6.1.概述

略过

6.2.无关性的基石

  • 平台无关性

各种不同平台的虚拟机与所有平台都统一使用的程序存储格式-字节码(ByteCode),是构成平台无关性的基石.

  • 语言无关性

实现语言无关性的基础是虚拟机和字节码存储格式。Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联。

6.3.Class类文件的结构

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符。根据java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有无符号数和表。

  • 无符号数属于基本的数据类型,以u1,u2,u4,u8来代表1个字节,2个字节,4个字节和8个字节。
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所以表都习惯性地以“_info”结尾。

Class文件格式

类型 名称 数量
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_count 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

6.3.1.魔数与Class文件的版本

每个Class文件的前4个字节称为魔数(Magic-Number),用于确定该文件是否为一个能被虚拟机接受的class文件。魔数之后存储的是版本号,第5和第6个字节为次版本号,第7和第8个字节为主版本号。 Java版本号从45开始。

很多文件存储标准中都使用魔数来进行身份识别,如gif,jpeg都在文件头中存有魔数。JAVA的class文件魔数为CAFEBABE。

6.3.2.常量池

主次版本号之后为常量池入口。因为常量池中常量的数量是不固定的,所以放一个u2类型的数据,代表常量池容量计数值(constant_pool_count)

类型 标志 描述
CONSTANT_Utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整型字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
CONSTANT_MethodHandle_info 15 表示方法句柄
CONSTANT_MethodType_info 16 标识方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

注意常量池的容量计数是从1开始,0用来表示不引用任何一个常量池项目。

// CAFEBABE为魔数
// 次版本为0000 ,转成十进制为0;主版本为0034,转成十进制为3*16+4=52;版本为JDK1.8
// 0022为常量数量为2*16+2=34个;
// 1:0A=10,类中方法的符号引用; 0004=4,类描述符CONSTANT_Class_info的索引值,001A=26,CONSTANT_NameAndType
// 2:07=7,全限定名常量项的索引;001B=27
// 3:0A=10,类中方法的符号引用;001C=28,001D=29
// 4:07=7,全限定名常量项的索引;001E=30
// 5:01,UTF-8编码的字符串占用的字节数;0006=6;3C 69 6E 69 74 3E = <init>
// 6:01,UTF-8编码的字符串占用的字节数;0003=3; 28 29 56 = ()V
// ......

// 可以使用javap工具:javap -verbose fileName

CA FE BA BE 00 00 00 34  00 22 0A 00 04 00 1A 07 
00 1B 0A 00 1C 00 1D 07  00 1E 01 00 06 3C 69 6E 
69 74 3E 01 00 03 28 29  56 
......

// 以下为常量池表
Constant pool:
   #1 = Methodref          #4.#26         // java/lang/Object."<init>":()V
   #2 = Class              #27            // com/xmasq/ems/gateway/GatewayApplication
   #3 = Methodref          #28.#29        // org/springframework/boot/SpringApplication.run:(Ljava/lang/Class;[Ljava/lang/String;)Lorg/springframework/context/ConfigurableApplicationContext;
   #4 = Class              #30            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/xmasq/ems/gateway/GatewayApplication;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               SourceFile
  #17 = Utf8               GatewayApplication.java
  #18 = Utf8               RuntimeVisibleAnnotations
  #19 = Utf8               Lorg/springframework/cloud/netflix/zuul/EnableZuulProxy;
  #20 = Utf8               Lorg/springframework/cloud/netflix/eureka/EnableEurekaClient;
  #21 = Utf8               Lorg/springframework/cloud/netflix/eureka/server/EnableEurekaServer;
  #22 = Utf8               Lorg/springframework/boot/autoconfigure/SpringBootApplication;
  #23 = Utf8               Lorg/springframework/context/annotation/ComponentScan;
  #24 = Utf8               basePackages
  #25 = Utf8               com.xmasq
  #26 = NameAndType        #5:#6          // "<init>":()V
  #27 = Utf8               com/xmasq/ems/gateway/GatewayApplication
  #28 = Class              #31            // org/springframework/boot/SpringApplication
  #29 = NameAndType        #32:#33        // run:(Ljava/lang/Class;[Ljava/lang/String;)Lorg/springframework/context/ConfigurableApplicationContext;
  #30 = Utf8               java/lang/Object
  #31 = Utf8               org/springframework/boot/SpringApplication
  #32 = Utf8               run
  #33 = Utf8               (Ljava/lang/Class;[Ljava/lang/String;)Lorg/springframework/context/ConfigurableApplicationContext;

6.3.3.访问标志

类型名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可以设置
ACC_SUPER 0x0020 如果是类这个标识都必须为真
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口和抽象类来说为真,其它为假
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码生成的
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举

例如0021 = 0020 | 0001 = public Class

6.3.4.类索引,父类索引与接口索引集合

类索引和父类索引都是一个u2类型的集合,接口索引则是一组u2类型的数据的集合。(单继承,多实现)

00 02(类索引:02) 00 04(父类索引:04) 00 00(接口集合)

6.3.5.字段表集合

用于描述接口或者类中声明的变量。字段包含类级变量以及实例级变量,但不含括在方法内部声明的局部变量。字段包含的信息有字段的作用域(public,private,protected),是实例变量还是类变量(static修饰符),可变性(final),并发可见性(volatile修饰符,是否强制从主内存读写),可否被序列化(transient修饰符),字段基础类型(基本类型,对象,数组),字段名称。

字段表结构:

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor 1
u2 attributes_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_TRAMSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由编译器自动产生的
ACC_ENUM 0x4000 字段是否enum

标识字符含义

标识字符 含义
B byte
C char
D double
F float
I int
J long
S short
Z boolean
V 特殊类型void
L 对象类型,如Ljava/lang/Object

举个存在字段表结构的例子:如00 01 00 02 00 05 00 06;00 01表示字段数量有1个,access_flags=0002为private,name_index=0005为第五项常量(为CONSTANT_Urf8_info,值为m),descriptor_index=0006为第六项常量(常量池字符串I)。原代码定义:private int m; 注意descriptor_index之后为attributes_count,例如定义final static int m = 123;那么就会有attributes_count

6.3.6.方法表集合

同字段表集合类似。

方法表结构:

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor 1
u2 attributes_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_SYNCHRONIZED 0x0020 方法是否为synchronized
ACC_BRIDGE 0x0040 方法是否由编译器产生的桥接方法
ACC_VARARGS 0x0080 方法是否接受不定参数
ACC_NATIVE 0x0100 方法是否为native
ACC_ABSTRACT 0x0400 方法是否为abstract
ACC_STRICTFP 0x0800 方法是否为strictfp
ACC_SYNTHETIC 0x1000 方法是否由编译器自动产生的

strictfp, 即 strict float point (精确浮点)。方法里的java代码,经过编译器编译成字节码指令后,存放在方法属性集表集合中一个名为“Code”的属性里面。

6.3.7.属性表集合

虚拟机规范预定义的属性

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类,方法表,字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当这个类为局部类或者匿名类时才能拥有这个属性,用于标识这个类所在的外围方法
InnerClasses 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性 检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature 类,方法表,字段表 由于java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 用于存储额外的调试信息
Synthetic 类,方法表,字段表 标识方法或字段为编译器自动生成
LocalVariableTypeTable 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类,方法表,字段表 为动态注解提供支持,用于指明哪些注解是运行时(实际上运行时就是进行反射调用的)可见的
RuntimeInvisiableAnnotations 类,方法表,字段表 用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotations 方法表 用于指明哪些方法参数是运行时可见的
RuntimeInvisibleParameterAnnotations 方法表 用于指明哪些方法参数是运行时不可见的
AnnotationDefualt 方法表 用于记录注解类元素的默认值
BootstrapMethods 类文件 用于保存invokedynamic指令引用的引导方法限定符

属性表结构

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length
  • 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 attributes_count 1
attribute_info attributes attributes_count

max_locals单位是Slot,对于byte,char,float,int,short,boolean,returnAddress等长度不超过32位,占用1个Slot,而long和double这两种64位占用2个Slot

// 0021 = public Class 参考6.3.3
// 00 02,类索引值为常量2; 00 04接口索引值为常量0; 00 00没有继承类
// 字段为00 00,没有字段
// 方法为00 02,有两个方法;0001=public;0005方法名索引5为<init>;0006描述符索引6为()V;0001一项属性;0007为常量“Code”,说明此属性为方法的字节码描述
// 00 00 00 2F=32+15=47为属性值的长度;操作数栈的最大深度和本地变量表的容量都为0001;字节码区域所占空间的长度位0005;2A = aload_0; B7 = invokespecial; 00 01 = 常量池1,为invokespecial参数;B1 = void;
               00 21 00  02 00 04 00 00 00 00 00 
02 00 01 00 05 00 06 00  01 00 07 00 00 00 2F 00 
01 00 01 00 00 00 05 2A  B7 00 01 B1 00 00 00 02 
00 08 00 00 00 06 00 01  00 00 00 15 00 09 00 00 
00 0C 00 01 00 00 00 05  00 0A 00 0B 00 00 00 09 
00 0C 00 0D 00 01 00 07  00 00 00 36 00 02 00 01 
00 00 00 08 12 02 2A B8  00 03 57 B1 00 00 00 02 
00 08 00 00 00 0A 00 02  00 00 00 18 00 07 00 19 
00 09 00 00 00 0C 00 01  00 00 00 08 00 0E 00 0F 
00 00 00 02 00 10 00 00  00 02 00 11 00 12 00 00 
00 1E 00 05 00 13 00 00  00 14 00 00 00 15 00 00 
00 16 00 00 00 17 00 01  00 18 5B 00 01 73 00 19 

// code内容。其中args_size=1,这个是指“this”这个参数
 Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return

//TODO 20200116

发布了39 篇原创文章 · 获赞 3 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览