【JVM第三篇--运行时数据区】程序计数器、虚拟机栈、本地方法栈
写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记。其观看地址如下:尚硅谷2020最新版宋红康JVM教程
一、运行时数据区
我们在编写Java程序时,使用JVM的流程主要如下所示:

虚拟机在执行Java程序时,会把它所管理的内存划分为不同的数据区域,即运行时数据区。有些数据区域是线程共享的,即这些区域会随着虚拟机的启动而创建,随着虚拟机的关闭而销毁。而另一些区域则是与线程对应,属于线程私有的。这些区域会随着线程开始而创建,随着线程的结束而销毁。
具体的划分如下:
多个线程共享的:堆、方法区
每个线程私有的:程序计数器、Java虚拟机栈、本地方法栈
图示如下:

关于Java线程:在虚拟机中,每个线程都与操作系统的本地线程是直接对应的。当Java的线程准备执行时,操作系统的线程也同时创建了。
二、程序计数器
程序计数器是一块很小的内存空间,可以认为是当前线程所执行的字节码的行号指示器,负责保存当前线程正在执行的字节码地址。
1、程序计数器的作用
① Java是支持多线程的,这也意味着CPU会不停地切换线程。虚拟机的多线程是通过CPU时间片轮转法来实现的,即每个线程占用CPU的时间是同等的,时间一到就会切换线程,如此重复,直到线程终止。这也意味着某个线程会因为时间片到而被挂起,所以当该线程再次获得时间片时,需要知道从哪个地方继续执行,此时虚拟机只需要读取程序计数器的值就可以知道要执行的字节码指令的位置了。这也是程序计数器是线程私有的原因,因为若程序计数器不是线程私有的话,当CPU切换线程时,会按照上一个线程的字节码指令的位置来执行当前线程的字节码指令,这显然是不正确的
② JVM的字节码解释器也需要通过改变程序计数器的值来明确下一条字节码指令。
2、程序计数器的特点
① 程序计数器是数据区中唯一没有规定OutOfMemoryError(内存溢出异常)的区域。
②如果一个线程执行的是Native本地方法,那么程序计数器的值为undefined。因为JVM在执行Native本地方法时,是通过JNI调用本地其他语言来实现的,而不是字节码。
三、虚拟机栈
栈是运行时的单位,堆是存储的单位。
栈解决程序的运行问题,即方法如何执行(或者如何处理数据)。
堆解决的是数据存储问题,即数据怎么放,在那放。
1、什么是虚拟机栈
虚拟机栈在每个线程开始时随之创建,虚拟机栈与数据结构中的栈一致,也是遵循先进后出的原则。
虚拟机栈进行出栈入栈操作的元素就是栈帧。每一个栈帧就对应着一个方法,也可以将栈帧理解为一个方法的运行空间。
一个栈帧入栈,意味着一个方法被调用,一个栈帧出栈,即一个方法执行完毕。则栈帧的入栈顺序就是方法的调用顺序。
有代码:
public class VMStackTest {
private static int i = 1;
public static void main(String[] args) {
VMStackTest test = new VMStackTest();
test.add();
System.out.println(i);
}
public void add(){
i++;
}
}
代码所示的虚拟机栈示例如下:

同一时刻,在同一线程中,只有位于虚拟机栈顶部的栈帧才是有效运行的,即只有位于栈顶的方法是正在执行的。执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。 如果在被调用的A方法中,又调用了B方法,那么对应的B方法的栈帧就会被创建,并被放在虚拟机栈的栈顶,成为新的正在执行的方法。
对于虚拟机栈来说,不存在垃圾回收问题。
Java方法有两种方法返回方式,一种是正常的方法返回,使用return指令;另一种是抛出异常。不管是哪一种,都会导致栈帧被弹出。
2、栈帧的内部结构
每个栈帧都存储着:
① 局部变量表
②操作数栈
③方法返回地址
④动态链接
⑤一些附加信息
图示如下:

(1)、局部变量表
局部变量表定义为一个数字数组,用于存放方法参数和方法内定义的局部变量。
局部变量表建立在线程的虚拟机栈上,是线程的私有数据,因此不存在数据安全问题。
虚拟机通过索引定位的方法查找相应的局部变量,索引的范围是从0~局部变量表最大容量。
局部变量表所需的容量大小是在编译期定下的,方法运行期间是不会该变局部变量表的大小的。
(2)、操作数栈
操作数栈也是一个栈的数据结构,操作数栈的最大深度也在编译的时候定下。
操作数栈的每一个元素可以是任意Java数据类型,在方法执行过程中,操作数栈的深度都不会超过最大值。
操作数栈的作用是: 随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。一个完整的方法执行期间往往包含多个这样出栈/入栈的过程。
(3)、方法返回地址
一个方法结束,有正常执行完毕退出和抛出异常(没catch),非正常退出两种。
无论是那种方式,在退出后都会返回到该方法被调用的位置。
正常退出时,调用者的程序计数器的值会作为返回地址,即调用该方法的指令的下一条指令的地址。但是异常退出时,返回地址要通过异常表来确定。
实际上,方法的退出就是当前栈帧出栈的过程。故需要恢复上一层栈帧的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈,并设置其程序计数器的值等,让调用者栈帧继续执行下去。
两种方法结束的区别是:因抛出异常而退出的方法不会给调用他的调用者栈帧任何返回值
3、虚拟机栈的常见异常
《Java虚拟机规范》中,允许虚拟机栈的大小是动态扩展的或者固定不变的。
这意味着,虚拟机栈会出现两种异常:
① StackOverflowError异常(栈溢出):
如果线程请求分配的栈容量超过Java虚拟机的最大容量时,就会抛出栈溢出异常。最简单的示例就是不停递归而不退出,就会报 StackOverflowError。
当虚拟机栈的大小为固定时,易出现该异常。
②OutOfMemoryError异常(内存溢出)
当虚拟机栈采用动态扩展时,一定程度上可以规避 StackOverflowError,但是虚拟机给每个线程分配到的内存空间是有限的,这也意味着隶属于线程的虚拟机栈的内存也是有限的,所以当虚拟机栈请求扩展的内存大小无法满足时,就会报该异常。
但是并不意味着,采用固定大小的虚拟机栈就不会报该异常,如果某个栈帧太大,超出虚拟机栈所有的内存也是可能的。
四、本地方法栈
(1)、本地方法
一个Native Method就是一个Java调用非Java代码的接口。在定义一个本地方法时,并不提供实现体(有些就像定义一个Java interface),因为实现体是由非Java语言在外面实现的。本地接口的作用是融合不同的编程语言为Java所用。
(2)、本地方法栈
虚拟机栈是用于管理Java方法的调用,本地方法栈是用于管理本地方法的调用。
①本地方法栈是线程私有的
②本地方法栈也是可实现固定或者可动态扩展的内存大小(这也意味着本地方法栈的异常也与虚拟机栈一致)
③本地方法栈存放着被native关键字标记的方法,在执行引擎执行时加载本地方法库。
④并不是所有的JVM都支持本地方法,故也无需实现本地方法栈
⑤在HotSpot JVM中,直接将本地方法和虚拟机栈合二为一。
五、扩展--设置栈内存大小
有代码如下:
public class StackOverError {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
在代码中采用了递归,以count++的次数来查看目前虚拟机栈的深度。其结果如下:

可见默认情况下,count的值为2467。
我们可以使用
-Xss:来规定了每个线程虚拟机栈的内存大小
如下,

设置栈的大小:--Xss1m或者-Xss1k (设置方法IDEA:Run --> Edit Configuration --> VM option)
结果如下,

可见当设置虚拟机栈的内存大小为2m时,count的值为233424。
故可看出使用参数 -Xss可以设置线程的最大栈空间,且虚拟机栈的大小直接决定了函数调用的大小。
【JVM第三篇--运行时数据区】程序计数器、虚拟机栈、本地方法栈的更多相关文章
- JVM 专题九:运行时数据区(四)本地方法栈
1. 本地方法栈 2. 什么是本地方法栈? Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用 本地方法栈,也是线程私有的. 允许被实现成固定或者是可动态拓展的内存大小 ...
- 【JVM第四篇--运行时数据区】堆
写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记.其观看地址如下:尚硅谷2020最新版宋红康JVM教程 一.堆的概述 JVM的运行时数据区如下: 一个Java程序运行起来对应着一个进程(操 ...
- 【JVM第五篇--运行时数据区】方法区
写在前面的话:本文是在观看尚硅谷JVM教程后,整理的学习笔记.其观看地址如下:尚硅谷2020最新版宋红康JVM教程 一.栈.堆.方法区的关系 虚拟机运行时的数据区如下所示: 即方法区是属于线程共享的内 ...
- JVM详解(三)——运行时数据区
一.概述 1.介绍 类比一下:红框就好比内存的运行时数据区,在各自不同的位置放了不同的东西.而厨师就好比执行引擎. 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的 ...
- JVM 专题十:运行时数据区(五)堆
1. 核心概述 1.1 堆概述 一个进程对应一个jvm实例,一个运行时数据区,又包含多个线程,这些线程共享了方法区和堆,每个线程包含了程序计数器.本地方法栈和虚拟机栈. 一个jvm实例只存在一个堆内存 ...
- JVM运行时数据区--Java虚拟机栈
虚拟机栈的背景 由于跨平台性的设计,java的指令都是根据栈来设计的.不同平台CPU架构不同,所以不能设计为基于寄存器的. 根据栈设计的优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样 ...
- jvm入门及理解(三)——运行时数据区(程序计数器+本地方法栈)
一.内存与线程 内存: 内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行.JVM内存布局规定了JAVA在运行过程中内存申请.分配.管理的策略,保证了JVM的 ...
- JVM 专题八:运行时数据区(三)虚拟机栈
2.虚拟机栈 1. 概述 1.1 虚拟机栈出现背景 由于跨平台性的设计,java的指令都是根据栈来设计的.不同平台CPU架构不同,所以不能设计为基于寄存器的. 优点是跨平台,指令集小,编译器容易实现, ...
- JVM系列之四:运行时数据区
1. JVM架构图 Java虚拟机主要分为五大模块:类装载器子系统.运行时数据区.执行引擎.本地方法接口和垃圾收集模块. 2. JDK1.7内存模型-运行时数据区域 根据<Java 虚拟机规范( ...
随机推荐
- shell携带附件,Linux下的自解压文件诞生了
初衷 windows下有自解压文件,直接双击就能释放文件,并且还能执行释放文件前后要执行的脚本.Linux下我也想要这样的功能,因为我希望直接拷贝一个shell脚本给别人,别人直接运行就能用,而不是一 ...
- 习题3-4 周期串(Periodic Strings, UVa455)
#include<stdio.h> #include<string.h> char s[100]; int main() { int T; scanf("%d&quo ...
- scp带密码拷贝文件
应用场景:将B服务器的文件传输到A服务器.核心命令: sshpass -p 123456 scp ubuntu@192.168.52.1:/home/ubuntu/"TEST"'' ...
- docker-docker-compose 安装
1.安装docker-compose(官网:https://github.com/docker/compose/releases) 安装: curl -L https://github.com/doc ...
- sql 存储过程 输出参数 输入参数
1.简单的存储过程 create procedure porc_name as select * from 表 go 调用时: exec proc_name 2. 带参数的存储过程 create pr ...
- 执行java -jar filename.jar提示没有主清单属性
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> ...
- 数位dp(贴一个模板=。=)
emmmm,之前看到大佬的博客感觉这个模板挺有用的,就贴了一个= = 然后解释什么的都有了就...... 数位dp一般应用于: 求出在给定区间[A,B]内,符合条件P(i)的数i的个数. 条件P(i) ...
- 变量分割技术、判别学习(discriminative learning method)
基于模型的优化方法(model-based optimization method): 小波变换.卡尔曼滤波.中值滤波.均值滤波: 优点:对于处理不同的逆问题都非常灵活:缺点:为了更好的效果而采用各种 ...
- vue-打包遇到的问题
vue-打包 打包后用iframe引入的html文件乱码 原因: 解决:用live server打开就不会乱码 生产环境移除所有的console命令 三种解决方法 发现vue-cli3.0在打包过程中 ...
- nb-iot模块实现联网的威力体现
窄带物联网(nb-iot)是一种越来越流行的方法,用于创建具有持久电池寿命,快速和功能丰富的互连设备系统.自2016年创建nb-iot以来,nb-iot设备和nb-iot模块中使用的技术得到了迅速发展 ...