内存是非常重要的资源,是硬盘和CPU的中间桥梁,承载操作系统和应用程序的实时运行.JVM内存布局规定java在运行过程中内存申请、分配、管理的策略,保证JVM高效稳定运行。不同的JVM对于内存划分和管理机制存在部分差异(如J9和JRocket没有方法区,而Hotspot存在) 。
运行时数据区包括堆、方法区、PC寄存器(即程序计数器)、虚拟机栈、本地方法栈。
其中黄色的为多个线程共享,绿色的为单独线程私有的,即:
JVM中程序计数寄存器用来存储下一条将要执行指令的地址(当前线程所执行的字节码的行号指示器),执行引擎从PC寄存器获取到指令地址后进行执行对应的指令。
PC寄存器特点:
为什么要用PC寄存器记录当前线程的执行地址?使用它存储字节码指令地址有什么用?
因为CPU需要不停的切换各个线程,此时切换回来后,就得知道接着从哪里开始继续执行。
JVM的字节码解释器需要通过PC寄存器的值来明确下一条应该执行什么样的字节码指令。
PC寄存器为什么被设定为线程私有的?
程序通常运行在多线程环境下,CPU会不停的做任务切换,会导致程序经常中断和恢复,为了保证程序运行结果分毫不差,所以每个线程都有独立的PC寄存器,分别独立的记录各个线程正在执行的当前字节码指令地址,这样各个线程之间便可以独立计算,从而不会出现相互干扰,保证程序运行结果正确。
由于跨平台设计,Java的指令都是根据栈来设计的,不同平台的CPU架构不同,所以不能设计为基于寄存器的。
栈是程序运行时基本单位(即解决程序如何运行、处理数据),堆是存储单位(即数据如何存储,存储在哪里)。
虚拟机栈特点:
一个虚拟机栈内部保存多个栈帧,栈帧是虚拟机栈的基本组成单位,且每个栈帧对应java程序中的一个方法。栈帧是一个内存区块,即:
注意:有的资料中也将动态链接、方法返回地址、其他附加信息统一称为栈数据区
操作数栈的作用就是在方法执行过程中,根据字节码指令,在栈中写入数据或者提取数据,即入栈(push)或者出栈(pop),比如执行求和,复制,交换等操作时。它的特点包括:
每个栈帧内部都包括一个指向运行时常量池中该栈帧所属方法的引用,该引用目的就是为了支持当前方法代码能够实现动态链接。
在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里,而动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
方法绑定机制:早期绑定和晚期绑定
静态链接:当一个字节码文件被装载进JVM时,如果被调用的目标方法在编译期可知,且运行期保持不变,这种情况下降调用方法的符号引用转换为直接引用的过程称之为静态链接
动态链接:如果被调用的目标方法在编译期无法被确定,只能通过运行期间将符号引用转换为直接引用,称为动态链接
绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,仅发生一次。而早期绑定可以对应静态链接,晚期绑定对应动态链接。
方法返回地址就是用来存放调用该方法的pc寄存器的值。方法结束有正常退出和异常退出:
本地方法: 一个Native方法就是一个Java调用非Java代码的接口,它的初衷就是融合C/C++程序;使用本地方法,我们可以用Java实现了Jre与底层系统的交互。
Java虚拟机用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。本地方法栈也是私有的。和Java虚拟机特点基本相同。
开发中JVM的栈遇到的异常
首先Java的虚拟机规范允许Java的栈大小是动态的或者固定不变的。
a.当采用固定大小时,若线程请求分配的栈容量超过虚拟机栈允许的最大容器容量,则JVM会抛出StackOverFlowError异常
b.当采取动态扩展时,当尝试扩展时候无法申请到足够内存或者没有足够内存创建对应的虚拟机栈,则会抛出OutOfMemoryError异常
调整栈大小,保证不出现栈溢出吗?
不能,只能延迟栈溢出的时间
分配的栈内存越大越好吗?
不是,它避免不了栈溢出等异常,而且会占用其他内存空间
垃圾回收是否会涉及到虚拟机栈?
不会,它只存在栈溢出或者OOM,因为栈只涉及到入栈和出栈,但不会GC
方法中定义的局部变量是否线程安全?
得具体问题具体分析,
如果只有一个线程可以操作该变量,则线程安全;
如果多个线程可以操作该变量,则线程安全;比如通过形参传入一个stringbuilder非安全对象,若其他线程再操作stringbuilder对象时,则可能会改变结果,造成线程不安全;