JVM -虚拟机执行子系统
Posted youzoulalala
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM -虚拟机执行子系统相关的知识,希望对你有一定的参考价值。
第三部分 虚拟机执行子系统
第六章 类文件结构
无关性的基石
多种语言(不仅仅是Java)编译成字节码(非机器码,与操作系统和指令集无关)Class文件,只要该文件符合虚拟机对Class文件的要求便可以在JVM中运行。
Class类文件结构
魔数 CAFEBABE、次版本号2u、主版本号2u;
常量池
常量池中的常量从索引1开始,索引0用于表示不引用常量池中的任何一个项目
常量池中主要存放两大类:字面量(如字符串和final常量)和符号引用(属于编译原理方面,包括了类和接口的全限定名、方法的名称和描述符、字段的名称和方法)
类或接口的访问标志2u 用于识别类或接口层次的访问信息。
类索引2u、父类索引2u、接口索引集合2u 确定类的继承和实现的关系
字段表集合:访问标志、名字索引、描述符、属性表
方法表集合:访问标志、名字索引、描述符、属性表
Code属性表:属性名、属性长度u4(但是字节码不能超过65535)、最大栈深度、最大局部变量内存、字节码长度、字节码、异常表数量、异常表、属性表数量、属性表。
第七章 虚拟机类加载机制
类的生命周期
第一步 加载
第二步 连接
验证、准备(为静态变量分配内存,并设置为默认值)、解析(顺序不确定)
第三步 初始化
按照静态变量的代码顺序进行初始化。
类的加载时机
没有强制的约束。
类加载过程
1 通过类的全限定名来获取定义此类的二进制字节流;
2 将字节流所代表的静态存储结构转化为方法区的运行时数据接口;
3 在堆中生成java.lang.Class对象,作为方法区这些数据的访问入口。
类的验证过程
文件格式验证、元数据验证、字节码验证、符号引用验证;
类的准备过程
为类变量(静态变量)在方法区中分配内存,并设置类变量的值为默认值。(常量设置为具体值)。
类的解析过程
将常量池内的符号引用替换为直接引用。
符号引用:用来描述引用对象的一组符号,可以是任何形式的字面量,与虚拟机实现的内存无关,与对象是否加载无关。
直接引用:指向引用对象的指针、偏移量或句柄。和虚拟机实现的内存布局有关,引用对象已经在内存中。
第七章 类加载机制
类的初始化
本质:执行类构造器<clinit>()方法的过程;类构造器由类变量赋值(先)和静态代码块语句合并(后);在多线程的情况下,类构造器会被加锁和同步(可能会造成初始化死锁)。
初始化时机:对一个类的主动使用会导致该类的初始化。主动使用包括:实例化、访问静态变量、访问静态方法、反射调用类、初始化启动类(含有main()方法);
几个类初始化的例子:
对于静态字段,只有直接定义这个字段的类才会被初始化。
使用某一类的数组,不会导致该类的初始化。(对于数组实例来说,其类型是由JVM在在运行期动态创建)
常量才编译期被放入常量池,其他类引用该常量与定义该常量的类没有关系。
一个接口初始化时,不要求其父接口全部都完成了初始化,只有真正用到父接口时才会对接口初始化。
类的唯一性
对于任意一个类,需要由类加载器和类型来确定该类的唯一性。(相同类但类加载器不同,不属于同一类)。
类加载器类型
启动类加载器:加载lib
扩展类加载器:加载 lib/ext
应用类加载器(系统类加载器):加载ClassPath路径上指定的库
自定义类加载器
双亲委派模式
启动类加载器是JVM自带的,由C++实现。其他类加载器由java实现,独立于虚拟机,都继承了ClassLoader。除了启动类加载器,其他均一般通过组合方式拥有自己的父类加载器。
每个加载器都优先尝试用父类加载,若父类不能加载则自己尝试加载;若成功则返回Class对象给子类,若失败则告诉子类让子类自己加载。所有都失败则抛出异常。
第8章 虚拟机字节码执行引擎
运行时栈帧结构
栈帧是用于支持虚拟机方法调用和方法执行的数据结构。
栈帧存储了方法的局部变量、操作数栈、动态连接和方法的返回地址等;
每一个方法从调用到执行结束返回对应这栈帧的入栈和出栈;
栈帧是线程(当前线程)私有的,只有栈顶的数据是有效的(当前栈帧)
局部变量表
用于存储方法参数(实例方法默认含有this参数)和方法内部定义的局部变量(必须赋值,不想静态变量会有系统初始值);
局部变量表的大小是在程序的编译期确定的(max_locals);
局部变量表中Slot可以重用,超过局部变量的作用域,则该变量所占用的Slot可以交给其他对象使用;被重用的对象会被GC清理。
局部变量表的容量已变量槽(Slot)为单位;java中数据大小在32位以内的数据有:byte boolean char short int float reference(对象引用) returnAddress(指向字节码的地址),这些类型均占用一个Slot; Java中64位数据由double long, 这些数据类型均占用2个Slot(高位在前,因为栈帧是线程私有的,所以对该类型数据的操作不会引起数据安全问题,也属于原子操作);
操作数栈
操作数栈的大小也是在编译期确定的,在方法的执行过程中伴随着大量的入栈和出栈的操作。
动态连接
指向运行时常量池中该栈帧所属方法的符号引用
方法返回地址
一个方法被执行后有两种退出方法的方式:正常完成退出和异常完成退出
正常完成退出时栈帧中一般会保存调用者的程序计数器值
异常完成退出不会向调用者返回值,根据异常处理表来处理。
附加信息
方法调用
一切方法调用在Class文件里面存储的都是符号引用,而不是方法在实际运行时的内存地址(直接引用)
解析
在类加载的解析阶段,会将静态方法、私有方法、实例构造器、父类方法(编译期可唯一确定)的符号引用解析为直接引用。
方法调用字节码
invokestatic:调用静态方法
invokespecial:调用私有方法、实例构造器、父类方法
invokevirtual:调用虚方法(final方法在字节码中使用invokevirtual,但属于非虚方法)
invokeinterface:调用接口方法
宗量
方法的接受者和方法的参数统称方法的宗量。静态分派根据多个宗量来实现分派,属于多分派;动态分派根据一个宗量来实现分派,属于单分派。
分派
静态分派(所有依赖静态类型来定位方法执行版本的分派都是静态分派)
变量的静态类型是编译期可以确定,实际类型只有在运行期才可以确定。
方法的重载是在编译期由编译器根据方法参数的静态类型和方法接受者的静态类型来确定的,所以静态分派属于多分派
动态分派(只用方法接受者作为选择的依据属于单分派)
invokevirtual指令运行时解析过程
1 操作数栈顶指向对象C的实例类型
2 在C中寻找符合的方法(常量池中的简单名称好描述符都一致),如果该方法可以访问则返回该方法的直接引用,不可以访问则抛出非法访问错误
3 如果在C中没有找到符合的方法,则由下自上向各父类寻找符合的方法
4 如果没有找到符合的方法则抛出虚函数异常
当执行invokevirtual、 invokeinterface 指令时,会对应地访问虚方法表和接口方法表(存放着各个方法的实际入口);对于重写的方法指向各自的数据类型,对于没有重写的方法,指向父类类型数据。
基于栈的指令集和基于寄存器的指令集
栈的指令集不受硬件寄存器的约束,移植性好,频繁访问内存,速度慢,指令多
基于寄存器指令集相反
以上是关于JVM -虚拟机执行子系统的主要内容,如果未能解决你的问题,请参考以下文章