1 基本概念
JVM是可运行Java 代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互。
2 运行过程
我们都知道Java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件,而字节码文件又通过Java 虚拟机中的解释器,编译成特定机器上的机器码 。
- Java 源文件—->编译器—->字节码文件
javac Person.java--->Person.class
- 字节码文件—->JVM—->机器码
每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是Java 为什么能够跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会存在多个虚拟机实例。程序退出或者关闭,则虚拟机实例消亡,多个虚拟机实例之间数据不能共享。
3 类加载机制
- ① 装载
(1)通过一个类的全限定名获取定义此类的二进制字节流
(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
(3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
- ② 链接
(1)验证:保证被加载类的正确性 文件格式验证/元数据验证/字节码验证/符号引用验证
(2)准备为类的静态变量分配内存,并将其初始化为默认值
(3)解析 把类中的符号引用转换为直接引用
- ③ 初始化
对类的静态变量,静态代码块执行初始化操作
4类加载器
双亲委派模型:
定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object 类。
5 运行时数据区
方法区:
(1)方法区是各个线程共享的内存区域,在虚拟机启动时创建
(2)用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
(3)虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非 堆),目的是与Java堆区分开来
(4)当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
(5)方法区在JDK8中就是Metaspace【元空间】,在JDK6或7中就是Perm Space【永久代】
(6)Run-Time Constant Pool在方法区分配
堆:
(1)堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享
(2)Java对象实例以及数组都在堆上分配
(3)当堆无法满足内存分配需求时,将抛出OutOfMemoryError异常
Java虚拟机栈:
是描述java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成 的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接、方法返回地址(Return Address)和附加信息。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。
a(){
b();
}
b(){
c();
}
c(){
}
javap -c Person.class > Person.txt
public static int calc(int op1,int op2){
op1 = 3;
int result = op1 + op2;
return result;
}
public static int calc(int, int);
Code:
0: iconst_3 // 将int类型常量压入操作数栈
1: istore_0 // 将int类型值存入局部变量0
2: iload_0 // 从局部变量0中装载int类型值
3: iload_1 // 从局部变量1中装载int类型值
4: iadd // 执行int类型的加法
5: istore_2 // 将int类型值存入局部变量2
6: iload_2 // 从局部变量2中装载int类型值
7: ireturn // 从方法中返回int类型的数据
本地方法区:
本地方法区和Java Stack 作用类似, 区别是虚拟机栈为执行Java 方法服务, 而本地方法栈则为Native 方法服务, 如果一个VM 实现使用C-linkage 模型来支持Native 调用, 那么该栈将会是一个C 栈,但HotSpot VM直接就把本地方法栈和虚拟机栈合二为一。如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行。
程序计数器:
一个JVM进程中有多个线程在执行,而线程中的内容是否能够拥有执行权,是根据CPU调度来的。
假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获
得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到
的位置。这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError 情况的区域。
(1)程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)
(2)如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址
(3)如果正在执行的是Native方法,则这个计数器为空。