深入理解Java虚拟机程序员Java学习笔记

《深入理解Java虚拟机》读书笔记4:类文件结构

2017-03-19  本文已影响131人  ginobefun

国内JVM相关书籍NO.1,Java程序员必读。读书笔记第四部分对应原书的第六章,主要介绍类文件结构的组成,并通过一个实例一步步了解各个部分的结构。

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

第六章 类文件结构

代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。

6.1 概述

由于最近十年内虚拟机以及大量建立在虚拟机之上的程序语言如雨后春笋般出现并蓬勃发展,将我们编写的程序编译成二进制本地机器码(Native Code)已不再是唯一的选择,越来越多的程序语言选择了操作系统和机器指令集无关的、平台中立的格式作为程序编译后的存储格式。

6.2 无关性的基石

Java虚拟机提供的语言无关性

6.3 Class类文件的结构

Class文件格式

下面我以自己本机写的一个简单的Java文件来学习其中各个部分的含义:


TestClass.java

使用javac编译成TestClass.class文件,使用16进制打开:


TestClass.class

使用javap命令输出Class文件信息:


javap TestClass

6.3.1 魔数和版本

6.3.2 常量池

常量池的项目类型 常量池中14种常量项结构说明1
常量池中14种常量项结构说明2

6.3.3 访问标志

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

6.3.5 字段表集合

字段表结构

6.3.6 方法表集合

6.3.7 属性表集合

虚拟机规范预定义的属性1
虚拟机规范预定义的属性2
6.3.7.1 Code属性

Java程序方法体中的代码经过javac编译后,字节码指令存放在Code属性,其属性表结构如下:

Code属性表的结构
6.3.7.2 Exceptions属性

方法描述时throws关键字后面列举的异常,和Code属性里的异常表不同。其属性表结构如下:

Exceptions属性表
6.3.7.3 LineNumberTable属性

用于描述Java源码行号与字节码行号之间的对应关系,它不是必须的,可以通过javac -g:none取消该信息。没有该信息的影响是运行时抛异常不会显示出错的行号,在代码调试时无法按照源码行来设置断点。

LineNumberTable属性
6.3.7.4 LocalVariableTable属性

用于描述栈帧中局部变量与Java源码中定义的变量之间的关系,它不是运行时必须的,可以通过javac -g:none取消该信息。如果没有这个属性,所有的参数名称都会丢失,取之以arg0、arg1这样的占位符来替代。

LocalVariableTable属性

其中local_variable_info项代表了一个栈帧与源码中局部变量的关联,如下所示:

local_variable_info结构
6.3.7.5 SourceFile属性

用于记录生成这个Class的源码文件名称,这个属性也是可选的。

SourceFile属性
6.3.7.6 ConstantValue属性

作用是通知虚拟机自动为静态变量赋值,只有被static关键字修饰的变量才可以用这个属性。对于非static类型的变量的赋值是在实例构造器<init>方法中进行的;而对于类变量有两种方式:在类构造器<clinit>方法中或者使用ConstantValue属性。目前Sun javac编译器的选择是:同时使用final和static修饰的变量且为基本数据类型或String类型使用ConstantValue属性初始化,否则使用<clinit>初始化。

ConstantValue属性
6.3.7.7 InnerClass属性

用于记录内部类与宿主类之间的关联。

InnerClass属性

其中number_of_class代表需要记录多少个内部类信息,每个内部类的信息都由一个inner_class_info表进行描述。

inner_class_info表的结构
6.3.7.8 Deprecated及Synthetic属性

Deprecated(不推荐使用)和Synthetic(不是由Java源码直接产生编译器自行添加的,有两个例外是实例构造器<init>和类构造器<clinit>)这两个属性都属于布尔属性,只存在有和没有的区别,没有属性值的概念。在属性结构中attribute_length的数据值必须为0x00000000。

Deprecated及Synthetic属性
6.3.7.9 StackMapTable属性

这是一个复杂的变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。

StackMapTable属性
6.3.7.10 Signature属性

一个可选的定长属性,在JDK 1.5发布后增加的,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息。这主要是因为Java的泛型采用的是擦除法实现的伪泛型,在字节码中泛型信息编译之后统统被擦除,在运行期无法将泛型类型与用户定义的普通类型同等对待。通过Signature属性,Java的反射API能够获取泛型类型。

Signature属性
6.3.7.11 BootstrapMethods属性

一个复杂的变长属性,位于类文件的属性表中,用于保存invokedynamic指令引用的引导方法限定符。

6.4 字节码指令简介

Java虚拟机的指令由一个字节长度的、代表着特定操作含义的数字(操作码)以及跟随其后的零至多个代表此操作所需参数(称为操作数)而构成。由于Java虚拟机采用面向操作数栈而不是寄存器的架构,所以大多数的指令都不包含操作数,只有一个操作码。

在指令集中大多数的指令都包含了其操作所对应的数据类型信息,如iload指令用于从局部变量表中加载int类型的数据到操作数栈中。

6.5 公有设计和私有实现

6.6 Class文件结构的发展

6.7 本章小结

本章详细讲解了Class文件结构的各个部分,通过一个实例演示了Class的数据是如何存储和访问的,后面的章节将以动态的、运行时的角度去看看字节码在虚拟机执行引擎是怎样被解析执行的。

系列读书笔记

扫一扫 关注我的微信公众号
上一篇 下一篇

猜你喜欢

热点阅读