目录
一. JVM概述
二. JVM的运行流程
三. JVM运行时数据区(面试常考重点)
1. 堆区(线程共享)
2. 虚拟机栈(线程私有)
3. 本地方法栈(线程私有)
4. 程序计数器(线程私有)
️5. 方法区/元数据区(线程共享)
四. 内存布局中的异常
1. 堆溢出
2. 栈溢出
JVM(Java Virtual Machine),为Java虚拟机,虚拟机是指通过软件模拟一个具有完整的硬件功能并且运行在完全隔离的环境中的完整的计算机系统,JVM是一台被定制过的现实中不存在的计算机
注意:选择题可能考
HotSpot VM:
现在广泛使用的虚拟机,HotSpot指热点代码探测技术,它能通过计数器找到最具有编译价值的代码,触发即时编译(JIT),通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡值
对JIT即时编译的解释:热点代码比如一个while循环,每次循环边运行边翻译,效率低,JIT即时编译就将此热点代码直接编译为机器码,后面就不需要翻译可以直接执行,效率高
一段Java代码是如何运行起来的呢?
通过java 类名会启动一个Java进程,系统为进程分配一块内存空间来执行进程的代码指令
有一条指令就会创建Java虚拟机
在Java虚拟机中会启动一个线程来执行main方法
Java虚拟机把class字节码的内容翻译为机器码,CPU来运行机器码
整体看Java代码从编译到运行时的流程:
说明:
Java程序在执行前先把Java代码转换为.class字节码,JVM先通过类加载器把字节码文件加载到Java进程内存的运行时数据区, 再将字节码翻译为底层系统指令所能识别的机器码,再交给CPU执行
JVM运行时数据区也叫作内存布局,由堆区,方法区,虚拟机栈,本地方法栈,程序计数器这五部分组成
jdk1.8把方法区称为元数据区,元数据区的常量池为运行时常量池
堆是最大的内存区域,程序运行时创建,程序退出时销毁,new创建对象都保存在堆中,字符串常量池也保存在堆中
堆中的新生代和老生代:新生代放新创建的对象,当经历了一定的GC次数之后还存活的对象就存放在老生代里
虚拟机栈的生命周期和线程相同,随着线程的创建而创建,线程的销毁而销毁,Java虚拟机栈描述的是Java方法执行的内存模型
每个方法都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息,常说的栈内存就指的是虚拟机栈
局部变量表:存放了编译器可知的基本数据类型,对象引用,方法参数,局部变量表所需的内存空间在编译期间就被分配好,进入一个方法,所需的局部变量表空间是确定的,在方法执行期间,不会改变局部变量表的大小
操作数栈:每个方法都会生成一个先进后出的操作栈
动态链接:指向运行时常量池的方法引用
方法返回地址:PC寄存器的地址
结合代码示例理解栈和栈帧
public class Test {
public static void swap(int m,int n){
int temp = m;
m = n;
n = temp;
}
public static void main(String[] args) {
int m = 10;
int n = 20;
swap(m,n);
System.out.println("m:"+m+" "+"n:"+n);
}
}
执行结果:m还是10,n还是20,没有发生交换
Java虚拟机可能调用系统函数(本地方法),也可能调用native方法(本地方法),native方法底层使用c/c++写的,本地方法也需要一定的内存空间,本地方法栈就是用来保存这些本地方法的
程序计数器用来记录当前线程执行代码的行号
如果当前线程执行的是Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址
如果当前线程执行的是native方法,这个计数器的值为空程序计数器是一块比较小的内存空间,是唯一一个在JVM规范中没有规定任何OOM情况的区域
用来存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据
jdk1.7,称为方法区,在Java进程的内存中
jdk1.8,称为元空间,属于本地内存(不在Java进程内存中)
运行时常量池:存放字面量与符号引用
字面量:字符串(jdk1.8移动到堆中),final常量,基本数据类型的值
符号引用:类和结构的完全限定名,字段的名称和描述符,方法的名称和描述符
可以设置JVM参数-Xms:设置堆的最小值,-Xmx:设置堆的最大值
设置参数为:-Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
import java.util.ArrayList;
import java.util.List;
public class Test {
static class OOMObject {
}
public static void main(String[] args) {
List list = new ArrayList<>();
while(true){
list.add(new OOMObject());
}
}
}
运行上面代码观察堆溢出(堆OOM):OutOfMemoryError
这里还需进一步分析是内存泄漏还是内存溢出
内存泄漏:对象等数据不用但是也无法被GC,比如io资源未释放,资源的内存就无法被回收,就可能导致内存泄漏
内存溢出:内存对象还存活,此时根据JVM堆参数与物理内存相比较检查是否应该把JVM堆内存调大,或检查对象的生命周期是否过长
如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常
运行下面代码,观察栈溢出:StackOverflowError
public class Test {
public static void method(){
int a = 0;
method();
}
public static void main(String[] args) {
method();
}
}
栈帧调用太深,一般在递归中出现,如递归没有返回值等等