什么是JVM的运行时数据区:
看下图:

java虚拟机在该虚拟机进程运行过程中定义了各种各样的运行时数据区。用于存储java程序运行时各种不同的数据。有些运行时数据区是在java虚拟机进程开始时就创建,进程结束时销毁,但是一些运行时数据区是在线程创建时跟着创建,线程终止时销毁。
JVM六大运行时数据区:

- 堆。
- 方法区。
- 虚拟机栈。
- 本地方法栈。
- 程序计数器。
- 运行时常量池。
由于运行时常量池是在方法区中分配的,所以又通常会分为五大运行时数据区。
堆(Heap):
定义:
- 堆是java虚拟机所管理的内存中最大的一块,在虚拟机进程启动时创建,在虚拟机进程终止时销毁,被所有的线程共享。
- 几乎所有的java对象实例以及数组都在堆上分配。
- 当堆无法满足内存分配的需求时,将会抛出OutOfMemoryError异常。
方法区(Method Area):
定义:
- 方法区会在虚拟机进程启动时创建,在虚拟机进程终止时销毁,被所有线程共享。
- 方法区主要用于存储已经被虚拟机加载的类信息、常量(存储在运行时常量池中)、静态变量、编译器编译后的代码,一些元信息等数据。
- 虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作Non-heap(非堆),主要目的是跟java堆区分开来。
- 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
- 方法区在jdk 8 中就是metaspace(元空间),在jdk6或7中就是Perm Space(永久代)。
- 运行时常量池在方法区中分配。
运行时常量池(Runtime constant Pool):
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
虚拟机栈(Java Virtual Machine Stacks)
定义:
- 虚拟机栈是一个线程执行的区域,是一个先进后出数据结构,保存着一个java线程的调用状态。换句话说,一个java线程的运行状态是由一个虚拟机栈保存的。所以虚拟机栈是线程私有的,随着线程的创建而创建,随着线程的终止而销毁。
- 虚拟机栈中存储的结构是栈帧(后面讲解)。
- 一个方法的执行开始,会把一个栈帧压入虚拟机栈中,方法执行完成时就把栈帧弹出虚拟机栈。
- 假如有如下代码:
public class XuNiJiZhan {
public static void a(){
d();
}
public static void c(){
}
public static void d(){
c();
}
public static void main(String[] args) {
a();
}
}
那么大概的线程就是这样:

解释:
- 从代码看出 线程先执行main方法,所以把main方法的栈帧压入虚拟机栈。
- 然后main方法中调用a方法,所以把a方法的栈帧压入虚拟机。
- 然后a方法调用d方法,所以把d方法的栈帧压入虚拟机。
- 然后d方法调用c方法,所以把c方法的栈帧压入虚拟机。
- 待得c方法执行完并返回时,c方法的栈帧会弹出,其他方法以此类推,直至虚拟机栈帧为空。
注意:
- 当一个线程的方法调用链太长,也就是说压入虚拟机栈的栈帧数量太大,超出一定数量时,就会报出StackOverflowError异常。
- 如果虚拟机可动态扩展的话,如果扩展时申请不到足够的内存,将会报出OutOfMemoryError异常。
栈帧:
- 每一个栈帧就对应一个一个方法的调用,可以理解为一个方法的运行空间。
- 每个栈帧中包括局部变量表、操作数栈(先进后出的栈结构)、动态链接(执行运行时常量池的引用),方法返回地址和一些附加信息。
- 局部变量表:存放了编译期可知的各种基本数据类型(int、double、char、float、boolean、short、byte、long)和对象引用、还有returnAddress(指向了一天字节码指令地址)。
- 对64位的double和long会占用两个局部变量空间(可以看作局部变量表的内存单位)。其余的数据类型占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法的栈帧的局部变量表所需的内存空间大小是完全确定的,且在方法运行区间不会改变。
看以下代码:
public class ZhanZhen {
public static int testFrame(){
int p1 = 3;
int p2 = 5;
int result = p1 + p2;
return result;
}
public static void main(String[] args) {
System.out.println(testFrame());
}
}
javap反编译后代码以下字节码指令(取testFrame方法部分,对照源码看食用效果更佳):
public static int testFrame();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=0
0: iconst_3 //将int型3压入操作数栈栈顶。
1: istore_0 //将操作数栈栈顶数值弹出并存入局部变量表中的第一个局部变量中。
2: iconst_5 //将int型5压入操作数栈栈顶。
3: istore_1 //将操作数栈栈顶数值弹出并存入局部变量表中的第二个局部变量中。
4: iload_0 //将局部变量表第一个局部变量的值压入操作数栈中。
5: iload_1 //将局部变量表第二个局部变量压的值入操作数栈中。
6: iadd //将栈顶的两个int型数值弹出并相加,相加后的值压入栈顶中。
7: istore_2 //将操作数栈栈顶数值弹出并存入局部变量表中的第三个局部变量中。
8: iload_2 //将局部变量表第三个局部变量的值压入操作数栈中。
9: ireturn //方法返回操作数栈栈顶的值。

本地方法栈(Native Method Stacks):
基本效果与虚拟机栈相似。只不过它是在线程执行本地native方法时才会使用该栈,也是线程私有的。
程序计数器(The PC Register):
- 占据一块较小的内存,记录着当前线程执行的虚拟机字节码指令地址。如果执行的方法时native方法,则该计数器为空。
- 因为在多线程环境下,cpu的调度会使得线程间切换。所以为了能在每个线程重新获取cpu时间片时能继续执行。所以每个线程会有一个记录当前执行的指令地址的程序计数器,使得线程能够继续执行。
- 每个线程都有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储。
- 此内存区域是唯一一个在java虚拟机规范中没有任何规定OutOfMemoryError情况的区域。
谢谢查看。一起进步,加油!!!

475

被折叠的 条评论
为什么被折叠?



