这是我参与更文挑战的第 2 天,活动详情查看: 更文挑战
本文正在参加「Java主题月 – Java 开发实战」,详情查看活动链接
PC寄存器
其中堆区和元数据区或叫方法区(包括CodeCache)是线程共享的,其他区域是线程独占的。
栈主要是运行时的内存,堆是存储时的内存。
概述:PC寄存器是对物理CPU寄存器的一种抽象模拟,主要用来存储指令相关的信息,也就是下一条将要执行的指令的地址,由执行引擎读取下一条指令。不会发生GC和OOM。
作用:记录下一条要执行的字节码指令。是程序流程的指示器,异常处理、线程恢复等操作都要依赖程序计数器来完成。
使用javap -v
指令查看字节码文件可以看到具体的指令地址
优势:
多线程执行的环境下,多个线程来回切换CPU执行权,需要记录每个线程的执行行数,方便CPU切换回来时从上一次执行的位置继续执行,基于这种情况,PC寄存器也是线程私有的,防止计数器被其他线程更改。
虚拟机栈
1.概述
每个线程的创建都会有一个虚拟机栈,线程私有。栈里存储的是若干个栈帧,一个栈帧对应一个Java方法。栈的生命周期和线程一致。栈的操作只有两种,方法的执行入栈,执行结束出栈,栈的效率仅次于PC寄存器。不存在GC,存在OOM。
2.作用
负责Java程序的运行,保存方法的局部变量、部分结构,参与方法的调用和返回。
3.栈内存设置
默认情况下栈内存是动态变化的,可以使用参数-Xss
设置栈的大小。
4.栈运行的原理
在一个线程中同一个时间点上,只能有一个栈帧在活动,即当前运行的方法对应的栈帧,也是栈的栈顶,如果方法A调用方法B,那么方法A对应的栈帧入栈,接着调用方法B,方法B对应的栈帧入栈,此时方法B就是当前栈帧,方法B结束后正常返回(return指令),虚拟机会丢弃当前栈帧,栈顶的栈帧变为方法A,如果方法B发生异常没有处理,那么当前栈帧会被弹出,异常会被返回给方法A,如果方法A也没有处理,那么弹出当前栈帧。**注意:**不同线程的栈帧不可以互相调用。
5.栈帧内部结构
栈帧主要有下面五个部分组成:
-
局部变量表:
- 是一个数字数组,主要存储方法参数、方法的局部变量和返回值。
- 局部变了表的大小是在编译器确定的,在方法运行时不会更改。栈帧的大小主要在于局部变量表。
- 局部变量表的基本存储单位是Slot(变量槽),32位以内的类型占用一个Slot,64位类型占用两个Slot(long和double),每一个Slot都有一个索引,指向局部变量表的值,如果一个64位的局部变量,使用前一个Slot索引就可以。如果该方法是由构造方法或实例方法(非static方法)创建的,那么index[0]保存的是this对象。Slot可以重复利用,如果一个变量超过了作用域,那么这个Slot会被下面的变量使用。
- 局部变量表中的变量是重要的垃圾回收根节点,只要局部变量中直接或简介引用的对象都不会被回收。
-
**操作数栈:**在方法的执行过程中,根据字节码指令向操作数栈中存入(入栈)和提取(出栈)数据。主要存储计算过程中的中间结果和过程中的中间变量,还有返回值。
-
动态链接:指向运行时常量池的方法引用,动态链接的作用就是将符号引用转换为调用方法的直接引用。
-
方法返回地址:存储该方法的PC寄存器的值,作用是在方法退出回到该方法被调用的位置。异常返回不会返回任何返回值。
-
附加信息(可忽略):对程序调试提高支持信息。
从代码和字节码去观察栈的内部变化
public int getSum(){
int m = 10;
int n = 20;
int k = m + n;
return k;
}
/**
* 查看字节码
* 0 bipush 10 寄存器记录指针位置 0 - 把10存入操作数栈,bipush表示以byte类型,但还是以int类存储(byte、short、char、boolean:都以int型来保存)
* 2 istore_1 寄存器记录指针位置 2 - 操作数栈出栈,存入局部变量表索引为1的位置,因为方法是非静态的,所以索引0位置存储的this,
* 3 bipush 20
* 5 istore_2
* 6 iload_1 从局部变量表取出索引为1的数据,并且存入操作数栈
* 7 iload_2 同上 此时操作数栈有两个值
* 8 iadd 出栈,iadd指令需要执行引擎翻译为机器指令,然后cpu执行操作,并且把结果存入操作数栈
* 9 istore_3 操作数栈取出栈顶元素,存入局部变量表索引为3
* 10 iload_3 从局部变量表取出索引为3的元素,存入操作数栈
* 11 ireturn 从操作数栈弹出然后返回
*/
复制代码
6.方法绑定机制
绑定就是一个字段、方法或类,在符号引用被转换为直接引用的过程,只发生一次。
早期绑定:目标方法在编译期可知,并且运行时保持不变,也就是使用静态链接的方式将符号引用转换为直接引用。
晚期绑定:被调用的方法无法在编译期确定,只能在程序运行时根据实际的类型绑定相关方法,使用的时动态链接将符号引用转换为直接引用。
7.虚方法和非虚方法
在方法编译器就确定了具体的调用版本,这个方法在调用时不可变的,那么这个方法就是非虚方法,比如:静态方法、私有方法、final修饰的方法、实例构造器、父类方法都是非虚方法,其他都是虚方法。因为方法的重写才出现的虚方法的调用。
相关指令:
- invokestatic:调用的静态方法,非虚方法。除了final修饰的
- invokespecial:调用init()、私有、父类方法,非虚方法。除了final修饰的。
- invokevirtaul:调用所有的虚方法。
- invokeinterface:调用接口方法,虚方法
- invokedynamic:动态解析调用的方法,主要是lambad
虚方法表:
每个方法都会存在一个虚方法表,表中存放着各个方法的实际入口。虚方法每次调用时都会向上找具体调用的方法(动态分派),为了提高性能,JVM在类的方法区建立一个虚方法表,用索引表代替查找。虚方法表在类加载的链接阶段解析的过程中创建。