# runtime
# 一、结构
- 程序计数器
- 方法区
- 堆
- 虚拟机栈
- 本地方法栈
分区有助于有效率的处理JVM中产生的对象和数据
其中方法区和堆是线程共享的,虚拟机栈。
# 程序计数器
程序计数器(Program Counter Register),也称PC寄存器。
字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
程序计数器可以理解为线程执行JVM指令的位置,而native方法没有jvm指令,所以native方法是计数器保持的是undifined,是java时,保持的是字节码指令的位置。
# 方法区
线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池也是方法区的一部分。已知,运用在string赋值时(非new),以及包装类Byte,Short,Integer,Long,Character,Boolean在-128~127范围内赋值时。
# 虚拟机栈
Java虚拟机栈是基于线程的。
在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。
栈里的每条数据,叫栈帧。每个方法调用时,都会创建栈帧,并入栈。完成方法调用时,则出栈。所有的栈帧都出栈后,线程也就结束了。
每个栈帧,都包含四个区域:
- 局部变量表
- 操作数栈
- 动态链接
- 返回地址
# 栈帧大小
栈帧的大小在编译时就已经确定,存放在这个方法的方法表的Code属性中。
# 局部变量表
局部变量表的容量(max_local)以变量槽(Variable Slot)为最小单位。
《Java虚拟机规范》中并没有明确指出一个变量槽应占用的内存空间大小。
一个变量槽可以存放一个32位以内的数据类型,像boolean、byte、short、char、int、float都可以用一个变量槽储存,long、double得用两个变量槽储存。
实例方法的第0个变量槽存储实例本身
局部变量表主要存放了编译器已知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
局部变量表中的变量槽是可以重用的
# 操作数栈
操作数栈的最大深度(max_stacks)在编译的时候被写入到了这个方法对应的方法表的Code属性中。
操作数栈的作用,就是在于为方法中的局部变量之间的加减乘除等操作提供一个内存空间。
# 动态链接
JVM在编译的时候,规定栈帧中保存一个这个栈帧对应的在方法区的方法的符号引用。
运行时将符号引用动态转化为直接引用,这个转化就是动态连接
# 返回地址
方法退出方式有两种:
- 正常调用完成
- 异常调用完成
区别是否异常完成没有返回值,但都会返回到最初方法被调用时的位置,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。
方法退出时出栈时可能执行:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。
# 本地方法栈
本地方法栈为虚拟机使用到的Native方法服务。 (在HotSpot 虚拟机中和 Java 虚拟机栈合二为一。)
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
# 堆
用于存放对象实例,几乎所有的对象实例以及数组都在这里分配内存
java8去掉老年代的原因? 1.字符串存在永久代当中,容易出现性能问题和内存溢出 2.类及方法的信息比较难确定其大小,因此对永久代的大小制定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。 3.永久代会为GC带来不必要的复杂度,并且回收效率偏低
# 对象分配
- 大对象直接进入老年代 -XX:PretentureSizeThreshold
- 长期存活的对象进入老年代 -XX:MaxTenuringThreshold = 15
- JVM中,如果Survivor区中的相同年龄的所有对象的大小总和大于Survivor空间的一半,那么这些同窗们就直接进入老年代
