一、JVM是什么
JVM是Java Virtual Machine(Java虚拟机)的缩写,通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行,这也是Java能够“一次编译,到处运行”的原因。
二、JVM整体结构图
三、JVM运行时数据区详解
1. JVM运行时数据区分为虚拟机栈、本地方法栈、程序计数器、堆和元空间(方法区)五部分。其中虚拟机栈、本地方法栈和程序计数器属于线程私有,而堆、元空间属于线程共享。
2. 虚拟机栈中存储方法、方法中的局部变量和运行时数据;本地方法栈和虚拟机栈类似,只不过本地方法栈中存储的是本地方法的一些数据信息;程序计数器用来存储程序代码的执行位置;每一个线程工作时,都会开辟自己的虚拟机栈、本地方法栈和程序计数器。
3. 堆区,存储对象信息以及数组;元空间存储类加载的相关信息,以及静态变量、常量、运行时常量池等。堆区和元空间是所有线程共享区域。
下面,以代码演示各个位置存储的信息:
//1.首先类加载器将类加载到元空间
public class Math {
//常量,存储在元空间
private final int num=100;
//静态变量,存储在元空间
private static User user=new User();
//main线程->main的线程栈,也就是虚拟机栈
//2. 执行main方法,将main方法压入栈
public static void main(String[] args) {
//3.在堆区创建Math对象,同时在main线程栈区存储对象的引用地址,指向堆区
Math math = new Math();
//4.将compute()方法压入栈
math.compute();
}
//一个方法对应一块栈帧区域,存储在栈区
public int compute() {
//局部变量,存储在栈区
int a = 1;
//局部变量
int b = 2;
//局部变量
int c = (a + b) * 10;
return c;
}
}
四、JVM运行时数据区各部分特点及作用
1.堆
堆用来存储对象和数组。堆是JVM上最大的内存区域,垃圾回收操作的对象就是堆。
堆空间一般是程序启动时就申请了,一般设置成可伸缩的。随着对象的频繁创建,对空间占用的越来越多,就需要定期的对不再使用的对象进行回收,这就是GC。
对基本数据类型对象(如byte、short、int、long、float、double、char),在方法体内申明时,会分配在栈中。
对于普通对象来说,JVM首先在堆上创建对象,然后在其他地方使用它的引用。
堆的几个重要参数:
-Xms: 堆的最小值
-Xmx: 堆的最大值
-Xmn: 新生代的大小
-XX:NewSize 新生代最小值(初始值)
-XX:MaxNewSize 新生代最大值
2. 元空间(方法区)
方法区也是一个线程共享的内存区。
方法区存储的内容有:类型信息(比如类全称、父类全称)、域信息(域名称、域修饰符private等)、方法信息(方法名称、方法修饰符、返回类型等)、字面量(字面量包括文本字符串、八种基本类型的值 、被声明为final的常量等)。
元空间的几个重要参数:
-XX:MetaspaceSize 元空间大小
-XX:MaxMetaspaceSize 元空间最大值
3. 虚拟机栈
虚拟机栈是当前线程在执行方法时存储所需的数据、指令、返回地址的一种栈结构(FILO:先进后出)。它的生命周期和栈保持一致,静态变量不栈。
每调用一个方法就会在栈里加一个栈帧。调用的方法执行完了,对应的栈帧就会出栈。栈帧分为4个区域,这4个区域就包含了执行Java方法时的全部内容。这四个区域分别是:局部变量表、操作数栈、动态链接、方法出口。
以我们文中的代码为例,虚拟机栈如下图:
虚拟机栈默认1M。如果我们不断的往虚拟机栈中加入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会溢出。
4. 程序计数器
由于现在都是多线程运行,而一个CPU在同一时刻只能运行一个线程,多个线程只能交替运行。程序计数器的作用就是记录当前线程下一条要运行的指令,这样保证了线程在切换回来时能回到正确的位置继续开始执行。
程序计算器是唯一不会发生内存溢出的地方。如果正在执行的是Native 方法,由于不是JVM执行,则这个计数器值为空(Undefined)。