深入理解Java虚拟机之虚拟机执行子系统(读书笔记)

 2019-12-22 10:39  阅读(932)
文章分类:JVM

类文件结构

1 Class类文件结构

Class文件的类结构是怎样的?

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

Class文件是一组以8位字节为基础单位的二进制流,各数据项紧密排列。空间占用8位以上的数据项按照**高位在前(最高位字节在地址最低位、最低位字节在地址最高位)**方式分割为若干个8位字节存储。

Class文件的数据结构分为哪两种?

无符号数。(u1、u2、u4、u8代表几个字节的无符号数,用来描述数字、索引引用、数量值、按照UTF-8编码的字符串值)

。(多个无符号数或其他表构成的复合数据类型,多以_info结尾)

当描述同一类型但数量不定的多个数据时,用一个前置的容量计数器+若干连续的数据项的形式,称之为某一类型的集合。

类型 名称 数量 说明
类型 名称 数量 说明
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 Interface的常量索引
U2 Fields_count 1 字段数量
Field_info fields Fields_count 字段的信息
U2 Methods_count 1 方法的数量
Method_info methods Methods_count 方法的信息
U2 Attributes_count 1 属性的数量
Attributes_info attributes Attributes_count 属性的信息

**魔数:**确定这个文件是否为一个能被虚拟机接受的Class文件。

常量池:存放两大类常量:(Constant_pool_count从1计数)

**字面量(Literal):**文本字符串、声明为final的常量;

**符号引用(Symbolic References):**类和接口的全限定名、字段的名称和描述符、方法的名称和描述符;

Access_flags:访问标志,用于判断:

这个class是类还是接口、是否定义是public类型、是否定义是abstract类型、如果是类是否是final。

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

字段表集合:用于描述接口或类中声明的变量。

字段包括类级变量和实例级变量,不包括方法内部局部变量。

包括:字段作用域、static修饰符、可变性final、并发可见性volatile、可否被序列化transient、字段数据类型、字段名称

方法表集合:

方法的定义通过访问标志、名称索引、描述符索引来表达。

方法里的Java代码,经过编译期编译成字节码指令后,存放在方法属性表集合中的一个名为“code”的属性里面。

属性表集合:

Class文件、字段表、方法表都可以携带自己的属性表集合。

虚拟机类加载机制

1 概述

什么是类加载机制?

虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。

Java语言中,类型的加载、连接和初始化过程都是在程序运行期间完成的。

2 类加载的时机

类从加载到虚拟机内存,到卸载出内存,它的生命周期是什么?

加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)。共7个阶段。

(加载、验证、准备、初始化、卸载的开始顺序固定,而解析可以在初始化之后进行,称为动态绑定或晚期绑定)

什么时候必须对类进行初始化?

1.遇到new、getstatic、putstatic、invokestatic这四条字节码指令的时候;

2.使用java.lang.reflect包的方法对类进行反射调用的时候;

3.初始化一个类时,其父类还没进行过初始化,先触发父类初始化;

4.虚拟机启动时,用户需要指定一个要执行的主类时;

5.JDK1.7,java.lang.invoke.MethodHanle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化;

3 类加载的过程

3.1加载

通过一个类的全限定名来获取定义此类的二进制字节流;

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;

在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

3.2验证

**验证目的:**就是确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

验证流程:

文件格式验证:保证输入的字节流能正确地解析并存储于方法区内;

元数据验证:对元数据信息进行语义校验;

字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑;

符号引用验证:对类自身以外(常量池)的信息进行匹配性校验;

3.3准备

正式为类变量(而非实例变量)分配内存并设置类变量初始值的阶段。(基本数据被赋初始零值,具体赋值在初始化阶段)

3.4解析

虚拟机将常量池内的符号引用替换为直接引用的过程。

(符号引用:以一组符号描述所引用的目标,符号可以是任何形式的字面量。符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。)

(直接引用:直接引用可以使直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,如果有了直接引用,那引用的目标在内存中必定存在。)

3.5初始化

初始化阶段是执行类构造器()方法的过程。

4 类加载器

虚拟机把“通过一个类的全限定名来获取定义此类的二进制字节流”这个加载阶段的动作放到java虚拟机外部去实现,实现这个动作的代码模块称为“类加载器”。

**Bootstrap ClassLoader:**由C++实现,是虚拟机自身的一部分。负责将存放在lib目录并被虚拟机识别的类库加载到虚拟机内存中。

**Extension ClassLoader(扩展类加载器):**负责加载lib\ext\目录下的所有类库。

**Application ClassLoader(应用程序类加载器/系统类加载器):**负责加载用户类路径(ClassPaht)上所指定的类库,开发者可以直接使用这个类加载器,一般情况下也是程序的默认类加载器。

什么是类加载器的双亲委派模型?

2019120001175\_1.png

父子间关系不是继承,而是组合(Composition)

**双亲委派模型工作过程:**如果一个类加载器收到了类加载的请求,首先自己不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,所有的加载请求最终都被传送到顶层的启动类加载器中,父类无法完成加载时,子类加载。

虚拟机字节码执行引擎

1 运行时栈帧结构

栈帧是用于支持虚拟机进行方法调用方法执行的数据结构,是虚拟机栈的栈元素。

每一个栈帧包括了局部变量表、操作数栈、动态连接、方法返回地址等。编译代码时,栈帧需要多大的局部变量表,多深的操作数栈都已经确定了,并写入到了方法表的Code属性中。因此一个栈帧分配多少内存,与程序运行期无关,仅仅取决于具体的虚拟机实现。

**局部变量表:**用于存放方法参数和方法内部定义的局部变量。在方法code属性的max_locals数据项中确定了方法的局部变量表的最大容量。

**操作数栈:**操作数栈的最大容量定义在code属性的max_stacks数据项中。

2 方法调用

方法调用唯一的任务就是确定被调用方法是哪一个,不涉及方法内部的具体运行过程。一切方法调用在class文件里面存储的都是符号引用,而不是直接引用。这个特性给Java带来了动态扩展能力,在类加载甚至运行期间才能确定目标方法的直接引用。

类加载的解析阶段,会将一部分符号引用转化为直接引用。(主要包括静态方法和私有方法)

Java虚拟机里面有5条方法调用字节码指令

invokestatic:调用静态方法;

invokespecial:调用实例构造器方法、私有方法、父类方法;

invokevirtual:调用所有的虚方法;

invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象;

invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行发方法

invokestatic、invokespecial指令调用的方法,可以在解析阶段确定。(静态方法、私有方法、实例构造器、父类方法这4类方法类加载时符号引用就解析为直接引用,称为非虚方法,其他方法称为虚方法)

动态方法调用(MethodHandle)和反射(Reflection)有什么区别?

Reflection模拟了java代码层次的方法调用

MethodHandle模拟了字节码层次的方法调用

类加载及执行子系统的案例与实战

1 Tomcat

Tomcat5目录有四个存放类库的目录:

/common:类库可被tomcat和web应用程序共同使用;

/server:类库可悲tomcat使用,对所有web应用程序不可见;

/shared:类库可被所有web应用程序共同使用,对tomcat自己不可见;

/WebApp/WEB-INF:类库仅仅可以被当前web应用使用,对tomcat和其他web应用程序不可见;

Tomcat6默认把/common、/server和/shared三个目录合并成一个/lib目录,因此

./lib目录:类库可以被Tomcat服务器本身和所有的Web应用程序共同使用。

./WebApp/WEB-INF目录:类库仅可以被应用程序使用,对其他的应用程序和Tomcat服务器不可见。

2019120001175\_2.png

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 深入理解Java虚拟机之虚拟机执行子系统(读书笔记)

相关推荐