思维导图
结构图为:
程序计数器(线程私有)
首先要清楚,线程在开始执行到结束的过程中,并不是一直都处于运行的状态的,中间是有线程切换的概念的,切换,就意味着线程的暂停以及恢复;
那么由暂停到恢复的这个过程,就需要记录线程执行到哪里被暂停了,后面还要从暂停的地址继续执行;所以就要引入程序计数器了,作用为:当前线程所执行的字节码的行号指示器(当前指令的地址),如果执行的是Native
方法,则为空;
虚拟机栈(线程私有)
虚拟机栈的内部为栈帧,每个线程都会单独创建一个栈帧,栈帧内的结构大致为:
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程;
Java虚拟机规范允许Java栈的大小是动态的或者固定不变的;
局部变量表
又称为局部变量数组或本地变量表;
顾名思义,存储的数据为方法的局部变量以及形参(方法接收的参数为形参)、对象引用等数据;
数据类型包括8种基本数据类型,对象引用(reference)以及returnAddress
类型;
因为是私有的,所以不存在线程安全的问题;
局部变量表里面存储的引用类型,一般都是指向堆空间;
栈结构:先进后出;后进先出
操作数栈
- 栈结构,不存在垃圾回收,局部变量表的数据可以押入栈中;
- 局部变量表会频繁的将变量押入栈,或取出栈;
- 栈的深度,取决于方法操作变量的次数;
- 主要用户保存计算过程中间结果,同事作为计算过程中变量临时的存储空间;
动态链接
每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用,包含这个引用的目的是为了支持当前方法的代码能够实现动态链接;
方法出口
首先要知道,一个方法的结束,有两种方式,一种是正常退出,另外一种是异常退出,无论哪种方式退出,在方法退出后都返回到该方法被调用的位置;
方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令地址;
异常退出的,返回的地址是要通过异常表来去而定的,栈帧中一般不会保存这部分信息;
方法返回地址、动态链接、一些附加信息也统称为帧数据区
本地方法栈(线程私有)
本地方法栈跟虚拟机栈的功能类似;
虚拟机栈用于管理 Java 函数的调用;而本地方法栈则用于管理本地方法的调用;
一般本地方法是使用C语言实现;
方法区(线程共享)
主要是用来存放已被虚拟机加载的「类相关信息」:包括类信息、常量池;
类信息包括了类的版本、字段、方法、接口和父类等信息;
常量池:
- 静态常量池
- 静态常量池也包括了我们说的「字符串常量池」
- 主要存储的是「字面量」以及「符号引用」等信息
- 运行时常量池
- 「类加载」时生成的「直接引用」等信息
方法区有永久代和元空间的概念,下面可以了解一下:
永久代为什么改为元空间?
「元空间」存储不在虚拟机中,而是使用本地内存,JVM 不会再出现方法区的内存溢出的问题,以往「永久代」经常因为内存不够用导致跑出OOM异常;
方法区是永久代还是元空间?
因为虚拟机的厂商不同,所以实现方法也不同;
HotSpot虚拟机在「JDK8前」用「永久代」实现了「方法区」;
而很多其他厂商的虚拟机其实是没有「永久代」的概念的;
在JDK8中,已经用「元空间」来替代了「永久代」作为「方法区」的实现了
主要是用来存放已被虚拟机加载的「类相关信息」:包括类信息、常量池
JDK1.8之前叫永久代,JDK1.8叫元空间
堆(线程共享)
堆内存分了好几区域,主要和垃圾回收机制有关,堆也是GC垃圾回收重点关注的区域;
这里单独开了篇JVM垃圾回收的文章,请移步;