其中蓝框中的红色部分代表一个进程一份,灰色代表一个线程一份,如一个运行的程序中有5个线程,这5个线程共享方法区和堆,而每个线程都有一份程序计数器、本地方法栈和虚拟机栈,共5组。
1. PC寄存器
PC寄存器用来存储指向下一条指令的地址,也就是即将要执行的代码。由执行引擎读取下一条指令。
在JVM中,每个线程都有自己的PC寄存器,就是用来记录当前线程的程序执行到哪了。线程私有,与线程的生命周期一致。
PC寄存器既没有GC,也没有OOM。
两个小问题
1.1 为什么要使用PC寄存器来记录线程的执行地址?
CPU需要切换不同的线程,切换回来以后,JVM需要知道这个程序要从哪里开始执行。通过改变PC寄存器的值来明确下一条指令位置。
2.2 PC寄存器为什么是线程私有的?
为了准确记录每个线程的执行位置,使线程之间的执行相互独立,不会互相干扰。
2. 虚拟机栈
每个线程在创建时都会创建一个虚拟机栈,内部保存一个又一个的栈帧,每个栈帧对应着一个方法的调用。虚拟机栈是线程私有的。生命周期与线程相同。它保存方法的局部变量(8种基本数据类型和对象的引用地址,具体的对象放在堆中)和部分结果,参与方法的调用和返回。
JVM对栈只有两种操作:进栈和出栈。
不存在GC(因为只有进栈和出栈两种操作),存在OOM。
栈中可能出现的异常
1)如果栈采用固定大小,当线程请求分配的栈容量超过JVM所允许的最大容量,将会出现StackOverflowError异常(栈溢出)。如在递归中没有设置退出循环的条件。
2)如果栈可以动态扩展,在扩展时没有申请到足够内存,或者创建新的线程时没有足够内存创建虚拟机栈,会出现OutOfMemoryError异常(内存不足)。
栈中存储什么?
栈的基本存储单位是栈帧,每个栈帧对应一个方法。
每个栈帧中存储什么?
局部变量表
定义为一个数字数组,用于存储方法参数和定义在方法体内的局部变量。
public static void main(String[] args) {
LocalVariable localVariable = new LocalVariable();
int i=10;
}
本地变量表有三个值:
分别为方法参数args,局部变量localVariable和i。
为什么静态方法中不能使用this?
当前帧为构造方法创建时,this会被放在init的本地变量表中,索引为0。当在静态方法中使用时,本地变量表不存在this这个变量,因此不能使用。
总结: 构造方法(init)和普通方法中会有this,放在index为0的slot,静态方法和main方法没有this。如果是double和long类型,占据两个slot,其余类型占据一个slot。
操作数栈
主要保存计算过程中的中间结果,同时作为计算过程中变量的临时存储空间,由数组实现。当一个方法开始执行时,一个新的栈帧就被创建,此时的操作数栈是空的,长度在编译期就已确定。虽然由数组实现,但并不能通过下标访问,只能由push和pop访问。
相关问题:
举例栈溢出的情况?
全称StackOverfolwError,如果栈是固定大小,通过-Xss设置,如果是动态扩容,内存不足时,会出现OOM。递归时会出现。
调整栈的大小,可以保证不溢出吗?
不能,只能保证StackOverflowError出现的晚一些。如原来的栈可以递归运行600次,扩容后达到800次,如果具体一个递归需要运行700次,可以保证不溢出,如果需要运行1000次,仍然会出现StackOverflowError。
运行时数据区哪些部分会出现Error,哪些部分会出现GC?
程序计数器都不会出现。本地方法栈和虚拟机栈(两个栈)会出现StackOverflowError,不会出现GC。堆和方法区会出现OOM,也会出现GC。
3. 本地方法栈
用于管理本地方法的调用,线程私有。
4. 堆
堆在JVM启动的时候就被创建,一个JVM实例对应一个堆内存,同时大小确定。
从使用角度来看,几乎所有的对象实例都在堆中分配内存。
方法执行结束以后,堆中的对象不会马上被移除,而是当空间不足,进行垃圾回收时才会被移除。
内存细分
Java7及之前堆内存逻辑上分为:新生区+养老区+永久区。
Java8及之后堆内存逻辑上分为:新生区+养老区+元空间。
堆空间设置大小时,为新生区大小+养老区大小,不包括元空间。
-Xms设置堆空间(新生+老年)初始内存,-Xmx设置堆空间最大内存。
-XX:NewRatio表示老年代与新生代的比值,一般不会调。
-XX:SurvivorRatio表示Eden:From:To,一般为8:1:1。
几乎所有的Java对象都是在Eden区被new出来的。
绝大部分Java对象的销毁都在新生区中进行。
对象分配过程:
Minor GC、Major GC和Full GC的区别:
Minor GC触发机制:
老年代GC(Major GC/Full GC)触发机制:
堆空间常用jvm参数:
5. 方法区
jdk7以前,把方法区称为永久代。jdk8开始,元空间取代永久代,并使用本地内存。方法区用于存储被虚拟机加载的类型信息、常量、静态变量等。
方法区是线程共享的。如果定义了太多的类,会抛出OOM(如加载了大量的jar包)。
栈、堆、方法区的关系:
运行时常量池与常量池
当把class文件中的常量池加载到内存时,就称为运行时常量池。
方法区的演进细节
6.总结
常见题目