JVM
定义
java虚拟机(java virtual Machine)是用于运行java应用程序的虚构个计算机.
作用:
是java程序"一次编译,到处运行"的关键,java程序编译时,不是直接编译成目标机器的机器码,而是编译成.class的二级制的字节码文件,再由目标机器上的JVM虚拟机把.class文件翻译为对应机器的机器码执行.
内存结构
五个部分:
-
程序计数器(Program Counter Register)
类似于传统cpu中的PC,会在每次执行一条指令后自增(+指令长度),指向下一条将要执行的指令的地址,JVM中的并发是通过线程切换并分配时间片执行实现的,任何时刻,一个处理器只会执行一条线程中的指令,为了线程切换后能恢复到正确的位置,每个线程都有一个独立程序计数器,这种内存称为"线程私有"内存.
-
栈(Java Stack)
虚拟栈描述的Java方法执行的内存模型,每个方法被执行的时候会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息,每个方法被开始调用到Return结束的过程,就是栈帧在虚拟栈中从入栈到出栈的过程.
局部变量表:存放编译期可知的各种基本数据类型,对象的引用 -
本地方法栈(Native Method Stack)
与Java Stack作用类似, 区别是Java Stack为执行Java方法服务, 而本地方法栈则为Native方法服务
-
堆(Heap)
几乎所有的对象实例和数组都在堆中进行分配空间,也是垃圾回收器主要的收集区,现代的VM采用分代收集法,分为新生代(Eden区、From Survivor区和To Survivor区) 和 老年代.
-
方法区(Method Area)
用于存储被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据.而在1.8中, 永久区已经被彻底移除, 取而代之的是元数据区Metaspace.与永久代不同, 如果不指定Metaspace大小, 如果方法区持续增长, VM会默认耗尽所有系统内存.
JVM类加载过程
- 加载
- 验证
- 准备
- 解析
- 初始化
加载
"加载"就是将.class文件读到内存中,“加载”的动作主要在classLoader.cpp(指包含类的子类)和classFileParser.cpp文件中实现
-
读取二进制字节流
JVM规范没有限制从本地读取.class文件,所以可以从jar包或者其他方式来读取.最终会返回ClassFileStream对象的指针
-
将字节流转换为运行时的数据结构
获取到指针后,会创建实例,并且解析ClassFileStream结构: 1.读取魔数并校验 校验jdk版本信息 2.读取常量池引用 包括常量和符号应用,符号引用指父类,接口,字段,放法等 3.读取访问标识并校验 类的类型class还是interface,访问类型public 还是抽象的 4.获取类的全限定名 读取当前类的索引,并在常量池中找到当前类的全限定名,读取常量池时,会获得常量池句柄,会标识全限定名的地址 5.获取父类或者接口信息,如果有继承或者实现,则需先加载父类和接口,如果已经加载则直接获取它们的句柄记录到本类中,并简单校验类名 6.读取字段信息和方法信息加载到本类的信息中,之后会评估类的大小,类加载完成之后,大小不会发生改变
-
生成java.lang.Class对象
1.计算虚拟函数表和接口函数表的大小 2.创建instanceKlass对象(.class文件对应的所有类信息) 3.创建Java镜像类并初始化静态域,通知JVM加载完成,方法区创建该类的元数据
验证
加载和验证是交叉进行的,验证在各个阶段都是存在的,验证二进制字节流代表的字节码文件是否合格,主要从一下几方面判断:
文件格式:参看class文件格式详解,经过文件格式验证之后的字节流才能进入方法区分配内存来存储。
元数据验证:是否符合java语言规范
字节码验证:数据流和控制流的分析,这一步最复杂
符号引用验证:符号引用转化为直接引用时(解析阶段),检测对类自身以外的信息进行存在性、可访问性验证
准备
“准备”阶段是为class或者interface中的静态变量赋初始值,如果常量无初始值,则默认赋值为java基本数据类型的默认值
类型 | 描述 |
---|---|
byte | 8位补码表示,默认值为0 |
byte | 用8位补码表示,初始化为0 |
short | 用16位补码表示,初始化为0 |
int | 用32位补码表示,初始化为0 |
long | 用64位补码表示,初始化为0L |
char | 用16位补码表示,初始化为”u0000”,使用UTF-16编码 |
float | 初始化为正0 |
double | 初始化为正0 |
boolean | 初始化为0 |
returnAddress | 初始化为字节码指令的地址,用于配合异常处理特殊指令 |
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
-
符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
-
直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那么引用的目标一定是已经存在于内存中。
1.类或接口的解析
2.字段解析
3.类方法解析
4.接口方法解析
初始化
类初始化阶段是类加载过程的最后一步,到了这个阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。
- 初始化阶段是执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
网友评论