1、作用:程序计数器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。
2、在JVM规范中,每个线程都有它自己的程序计数器,是线程私有的,声明周期与线程的生命周期保持一致。
3、任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址:或者,如果实在执行native方法,则是未指定值。
4、字节码解释器工作时就是通过改变这个计数器的值来读取下一条需要执行的字节码指令。
public class PCRegisterTest {
public static void main(String[] args) {
int i = 10;
int j = 20;
int k = i + j;
}
}
javap -v PCRegisterTest.class
1)为什么使用程序计数器记录当前线程的执行地址呢?
因为CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪个位置开始继续执行。JVM的字节码解释器就需要通过改变程序计数器的值来明确一下条应该执行什么样的字节码指令。
2)程序计数器为什么会被设定为线程私有的?
为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个程序计数器,这样以来各个线程之间可以进行独立的计算,从而不会出现相互干扰的情况。
CPU时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称为它的时间片。
在宏观上:我们可以同时打开大哥应用程序,每个程序并行不悖,同时运行。
在微观上:由于只有一个CPU,一次只能处理程序要求的一部分。如何处理公平,一种方法就是引入时间片,每个程序轮流执行。
1、栈是运行时的单位,而堆是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。堆解决的是数据存储的问题,即数据怎么放、放在哪里。
2、Java虚拟机栈是什么?
1、栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
2、JVM直接对Java栈的操作只有两个:
我们可以使用参数-Xss选项来设置线程的最大栈空间,栈的大小直接决定了函数用的最大可达深度。
1、演示栈内存溢出StackOverflowError的情况
/**
* @author aaa
* @date 2022-07-24 15:22
* @description StackOverflowError 11412
*/
public class StackErrorTest01 {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
运行结果:
栈内存溢出,默认情况 count = 11412
2、Idea中设置栈空间大小
设置完栈空间大小后,再运行
发现 count=2458
每个栈帧中存储着:
1、局部变量表基本概念
局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。
2、关于槽的理解
3、通过Idea中的工具分析局部变量表
如果是非静态方法的普通方法,局部变量表索引0的位置存放的是this对象引用。
1、每个独立的栈帧中除了包含局部变量表外,还包含一个后进先出的操作数栈,也可以称之为表达式栈。
2、操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)。
3、操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
4、操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push)和出栈操作来完成一次数据访问。
5、代码追踪
public class PCRegisterTest {
public static void main(String[] args) {
int i = 15;
int j = 8;
int k = i + j;
}
}
分析:
1、首先是操作数栈是空的,程序计数器值为0
2、执行bipush操作入栈时,会先将15这个数值压入操作数栈中的栈顶位置。
3、程序计数器的值为1,向下执行,将操作数栈中的值15存放到局部变量表中,程序计数器的值再加1。数值8的存储过程和上面一样,都会将值存放到局部变量表中,不再赘述。
4、执行iload_1和iload_2指令时会将局部变量表中取出数据后并存放到操作数栈中,
5、接下来执行iadd操作,两个数再次出栈并通过执行引擎执行两数之和的操作,将得到的结果存储在操作数栈中。执行istore_3指令,将结果以int的方式存储在局部变量表中。
1、每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接。
2、在字节码文件中有一个常量池
1、存放调用该方法的PC寄存器的值。
2、一个方法的结束,有两种方式:
一个Native Method就是一个Java调用非Java代码的接口。
使用native修饰的方法称为本地方法。