【JVM之运行时数据区1】程序计数器、虚拟机栈、本地方法栈

一、运行时数据区

我们在编写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虚拟机栈执行原理深入详解

2020最新-JVM-Java虚拟机-从入门到精通-尚硅谷

知乎-Java虚拟机—栈帧、操作数栈和局部变量表

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342