一、jvm基本介绍
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,具体实现有很多,以下内容如果不额外声明,默认是HotSpot JVM。JVM它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。简单来说JVM就是用来解析和运行Java程序的
内存结构中的各个区的详细情况可参照下图(JDK8及以后的版本)
JVM的好处:
一次编写,到处运行
自动内存管理,垃圾回收功能
数组下标越界检查
多态
JDK、JRE、JVM的关系如下:
二、jvm内存
2.1程序计数器
程序计数器的英文全称是Program Counter Register,又叫程序计数寄存器。Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。CPU只有把数据装载到寄存器才能够运行。我们的程序计数器是Java对物理硬件的屏蔽和抽象,他在物理上是通过寄存器来实现的。寄存器可以说是整个CPU组件里读取速度最快的一个单元,因为读取/写指令地址这个动作是非常频繁的。所以Java虚拟机在设计的时候就把CPU中的寄存器当做了程序计数器,用他来存储地址,将来去读取这个地址。
每个线程都有自己的程序计数器,这样当线程执行切换的时候就可以在上次执行的基础上继续执行。仅仅从一条线程线性执行的角度而言,它的作用可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
特点:
- 程序计数器是线程私有的,这是为了能够准确记录各个线程正在执行的当前字节码指令地址,最好的办法就是为每一个线程分配一个程序计数器。每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响
- 执行java方法时,程序计数器是有值的,执行native本地方法时,程序计数器的值为空(Undefined),因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的
- 此内存区域是唯一一个在Java虚拟机规范中没有规定任何内存溢出情况的区域,因为程序计数器存储的是字节码文件的行号,而这个范围是可知晓的,在一开始分配内存时就可以分配一个绝对不会溢出的内存
程序计数器在Java虚拟机中是一个非常重要的概念,它通常用来存储当前线程执行的字节码指令的地址或行号。程序计数器是线程私有的,每个线程都有自己的程序计数器,用于记录当前正在执行的指令的位置。
在Java虚拟机中,程序计数器在执行Java方法时会记录当前正在执行的字节码指令的地址或行号,帮助虚拟机控制程序流程。程序计数器也被用于实现线程切换和指令重排序等功能。
因此,可以说程序计数器存储了当前线程执行的字节码的行号或地址,帮助虚拟机执行正确的指令序列。
2.2虚拟机栈
java虚拟机栈(Java Virtual Machine Stacks)是每个线程运行时所需的内存。每个栈由多个栈帧(Stack Frame)组成,每个方法执行都会创建一个栈帧,对应着该方法调用时所占用的内存,栈帧包含局部变量表、操作数栈、动态连接、方法出口等。每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法。
2.2.1栈与栈帧
每一个方法的开始执行到执行完成,都对应着一个栈帧在虚拟机中从入栈到出栈的过程。栈顶的栈帧就是当前执行方法的栈帧,称为当前栈帧(Current Stack Frame),这个栈帧关联的方法被称为当前方法(Current Method)。当这个方法调用其他方法的时候就会创建一个新的栈帧,这个新的栈帧会被放到虚拟机栈的栈顶,变为当前的活动栈,只有这时该栈帧的局部变量才能被使用。当这个栈帧所有指令都完成的时候,这个栈帧就会被移除,之前的栈帧变为活动栈,前面移除栈帧的返回值变为这个栈帧的一个操作数。
- 栈(Stack)
- 栈是一种数据结构,遵循后进先出(LIFO)的原则,即最后压入栈的元素最先弹出。
- 在计算机内存中,栈通常用于存储方法调用、局部变量和临时数据等信息。每个线程都有自己的栈,用于管理方法调用和执行过程中的数据。
- 栈的操作包括压栈(push)和弹栈(pop),通过栈指针来管理栈顶位置。
- 栈帧(Stack Frame)
- 栈帧是栈中的一个元素,用于表示方法调用的信息和局部变量的存储空间。
- 每次方法调用时,都会创建一个新的栈帧,包含方法的参数、局部变量、操作数栈等信息。
- 栈帧通常包括局部变量表、操作数栈、动态链接、返回地址等部分,用于支持方法的执行和控制流。
- 当方法执行结束时,对应的栈帧会被弹出栈,恢复到上一个方法的执行状态。
在Java中,当一个方法被调用时,会创建一个新的栈帧并压入栈中,用于执行方法的代码和管理局部变量。如果方法中包含循环,每次循环迭代都会创建新的栈帧。当循环条件不满足时,方法执行结束,对应的栈帧会被弹出栈。例如:
public class Main {
public static void main(String[] args) {
int result = loopMethod(5);
System.out.println("Final Result: " + result);
}
public static int loopMethod(int n) {
if (n == 0) {
System.out.println("Base Case Reached");
return 0;
} else {
System.out.println("Current n: " + n);
return n + loopMethod(n - 1); // Recursive call
}
}
}
在这个示例中, loopMethod 是一个递归方法,每次递归调用时都会创建一个新的栈帧。当 n 的值递减到0时,满足条件返回,对应的栈帧会被弹出栈。程序最终会输出每个栈帧的执行结果,并在递归结束后输出最终结果。
在Java中,栈帧的创建和销毁是由JVM自动管理的,开发者无需手动管理栈帧。递归方法是一种常见的情况,它会在栈中创建多个栈帧,直到达到终止条件才会依次弹出栈帧。
未完待续。。。。。。。。。。。。。。。