目录
7. JVM运行时数据区
7.1. 运行时数据区-总览
- 运行时数据区的概念:是JVM运行Java程序过程中管理内存的区域
- 运行时数据区的组成总览
- 线程不共享的含义:每个线程创建后,内部都会创建自己的程序计数器,Java虚拟机栈,本地方法栈对应的数据,自己维护数据,不共享,安全性高,当线程回收时这些区域的内存也随着回收
- 线程共享的含义:每个线程都可以访问方法区和堆中的数据,是共享的,是线程不安全的
7.2. 运行时数据区-查看内存对象
- JDK自带的hsdb工具查看JVM内存信息,工具位于JDK安装目录下lib文件夹中的sa-jdi.jar文件中
- 启动命令:java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
7.3. 运行时数据区-程序计数器
7.3.1. 程序计数器-作用
- 程序计数器的作用:程序计数器(Program Counter Register)也叫PC寄存器,内部存放了接下来要执行的字节码指令的地址,交给解释器执行,可以实现分支,跳转,异常等逻辑,在多线程情况下,JVM需要程序计数器记录CPU切换前的地址并继续执行,程序计数器只存储一个固定长度的内存地址,不会内存溢出
7.3.2. 字节码指令执行流程
- 字节码指令执行的流程:字节码指令和偏移量最初保存在字节码文件中,类加载器将字节码文件读取到内存之后,字节码指令也就保存在内存中原文件中的偏移量也被替换成内存地址,每一行字节码指令都有自己的地址,字节码指令最终要交给解释器去执行,所以解释器就必须知道需要解释的字节码指令在哪,这个地址就由程序计数器交给解释器
程序计数器在物理上的实现是寄存器,是整个CPU里读取速度最快的单元
程序计数器也就是Java对物理硬件的屏蔽和抽象
7.4. 运行时数据区-Java虚拟机栈
7.4.1. 栈-概述
- 栈的介绍:Java虚拟机栈采用栈的数据结构来管理方法调用中的基本数据,先进后出,每一个方法的调用使用一个栈帧来保存
- 栈的组成:
-
- 栈:一个线程运行所需要的内存空间,一个栈由多个栈帧组成
- 栈帧:一个方法运行所需要的内存空间
-
-
- 活动栈帧:一个线程中只能有一个活动栈帧
-
- 栈的生命周期:栈随着线程的创建而创建,而回收会在线程销毁时进行
- 栈的执行流程:
-
- 栈帧压入栈内执行方法
- 执行完毕释放内存
- 若方法间存在调用,那么会压入被调用方法入栈,执行完后释放内存,再执行当前方法,直到执行完毕,释放所有内存
7.4.2. 栈帧-组成
7.4.2.1. 栈帧-帧数据
- 帧数据的作用:包含动态链接,方法出口,异常表的引用
-
- 动态链接:保存的是符号引用到内存地址的映射关系,主要保存的是其他类的属性或方法,字节码指令运行时可以根据动态链接快速获取到运行时常量池的数据
- 方法出口:方法出口指的是方法在正确或者异常结束时,当前栈帧会被弹出,同时程序计数器应该指向上一个栈帧中的下一条指令的地址。所以在当前栈帧中,需要存储此方法出口的地址
- 异常表:异常表存放的是代码中异常的处理信息,包含了try代码块和catch代码块执行后跳转到的字节码指令位置。
7.4.2.2. 栈帧-操作数栈
- 操作数栈作用:存储方法执行过程中需要计算的操作数以及操作结果.
-
- 计算过程:从操作数栈中弹出操作数、执行运算操作,并将结果重新压入操作数栈中
- 操作数栈的生命周期:当方法执行时,创建一个对应的栈帧,栈帧中包括了该方法操作数栈,当方法执行结束时,对应的栈帧也会被销毁,在编译期就可以确定操作数栈的最大深度
7.4.2.3. 栈帧-局部变量表
- 局部变量表的作用:存储方法执行过程中所有的局部变量,字节码文件编译时就确定了局部变量表的内容
- 字节码文件中的局部变量表:每个局部变量表有编号和生效范围
- 栈帧中的局部变量表:栈帧中的局部变量表是一个数组,数组中每一个位置称之为槽(slot),long和double类型占用两个槽,其他类型占用一个槽。
- 局部变量表保存的内容:实例方法的this对象(0号位置),方法的参数,方法体中声明的局部变量。
- 局部变量表的运行优化机制:局部变量表中的槽是可以复用的,一旦某个变量失效了(代码块中的变量执行完毕),当前槽就可以再次使用
- 局部变量表的生命周期:当方法执行时,创建一个对应的栈帧,栈帧中包括了该局部变量表,当方法执行结束时,对应的栈帧也会被销毁
7.4.3. 栈-执行流程
执行流程演示:
- 栈内随着方法调用,栈帧一个个被压入栈内,随着方法执行完毕,方法一个个被弹栈而出,定义的变量的内存空间也随着方法执行完毕释放
7.4.4. 栈-内存溢出
- 栈内存溢出的情况:Java虚拟机栈如果栈帧过多,占用内存超过栈内存可以分配的最大大小就会出现内存溢出,此时会出现StackOverflowError的异常
- 栈帧过多:递归调用,死锁,死循环
- 栈帧过大
7.4.5. 栈-设置栈内存大小
- 设置栈大小参数:-Xss或-XX:ThreadStackSize=1024
-
- 语法:-Xss栈大小或-XX:ThreadStackSize=栈大小
- 单位:字节(默认,必须是1024的倍数)、k或者K(KB)、m或者M(MB)、g或者G(GB)
- 栈最大最小值:jdk8中最大180k,最大1024mb
7.4.6. 栈相关问题
7.4.6.1. 垃圾回收是否涉及栈内存?
不涉及,每次方法执行完毕之后,栈内存就会直接释放,并不会堆积内存,垃圾回收的是堆内存中不使用的内存
7.4.6.2. 栈内存分配越大越好吗?
在程序执行时,可以通过-Xss size指定栈内存的大小,默认大小为1024KB也就是1M,一个线程使用一个栈内存,如果栈内存设置过大,那么单个线程占用的内存就会过大,在总内存空间一定的情况下,这样反而减少了线程数量,降低了性能
7.4.6.3. 方法内的局部变量是否线程安全?
要判定一个变量是否为线程安全,就要查看这个变量是为多个线程共有的还是一个线程私有的.
要判断方法中的成员变量是否为线程安全,要查看这个成员变量是否脱离了方法的作用范围,有两种情况是线程不安全的:
- 变量作为参数传入方法中,这样其他线程就可能访问到该变量,所以不是线程安全的
- 变量作为返回值返回,这样其他线程可能拿到该变量并操作,所以不是线程安全的