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

类文件结构

1 Class类文件结构

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

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

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

 

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

无符号数。(u1u2u4u8表明几个字节的无符号数,用来描述数字、索引引用、数量值、按照UTF-8编码的字符串值)服务器

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

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


类型布局

名称编码

数量spa

说明

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_count1计数

字面量(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.遇到newgetstaticputstaticinvokestatic这四条字节码指令的时候;

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

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

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

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

 

3 类加载的过程

3.1加载

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

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

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

 

3.2验证

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

验证流程:

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

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

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

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

 

3.3准备

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

 

3.4解析

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

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

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

 

3.5初始化

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

 

4 类加载器

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

 

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

 

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

 

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


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


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

双亲委派模型工做过程:若是一个类加载器收到了类加载的请求,首先本身不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成,全部的加载请求最终都被传送到顶层的启动类加载器中,父类没法完成加载时,子类加载。

 

虚拟机字节码执行引擎

1 运行时栈帧结构

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

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

 

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

 

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

 

2 方法调用

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

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

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

invokestatic:调用静态方法;

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

invokevirtual:调用全部的虚方法;

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

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

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

 

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

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

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


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

1 Tomcat

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

/common:类库可被tomcatweb应用程序共同使用;

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

/shared:类库可被全部web应用程序共同使用,对tomcat本身不可见;

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

 

Tomcat6默认把/common/server/shared三个目录合并成一个/lib目录,所以

./lib目录:类库能够被Tomcat服务器自己和全部的Web应用程序共同使用。

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