写点什么

必知必会 JVM 二 - 运行时数据区

  • 2021 年 11 月 12 日
  • 本文字数:3238 字

    阅读完需:约 11 分钟

2、运行时数据区介绍


2.1 程序计数器


2.2虚拟机栈


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


2.3本地方法栈


2.4 方法区


2.5 heap(堆)




1、什么是运行时数据区




恩......如果我说什么是 Java 的内存模型,估计大家都会脱口而出,程序计数器、方法栈、本地方法栈、元空间、堆。而且很自信的觉得自己说的一点都没错。are?you?really?



其实对于 java 的内存模型和运行时数据区很多同学都搞不清楚。java 的内存模型是 JMM(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM 在计算机内存(RAM)中的工作方式,如果我们要想深入了解 Java 并发编程,就要先理解好 Java 内存模型。可以见我的并发编程专题文章,并发编程十二-Java内存模型以及底层实现原理 了解 JAVA 的内存模型。


程序计数器、方法栈、本地方法栈、元空间、堆这些统称为 java 运行区域,也叫运行时数据区。


2、运行时数据区介绍




2.1 程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。


由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器内核都只会执行一条线程中的指令。


因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。


如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。

2.2 虚拟机栈

与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。


虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame,是方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。如下图



1. 局部变量表


局部变量表是存放方法参数和局部变量的区域。 局部变量没有准备阶段, 必须显式初始化。如果是非静态方法,则在 index[0] 位置上存储的是方法所属对象的实例引用,一个引用变量占 4 个字节,随后存储的是参数和局部变量。字节码指令中的 STORE 指令就是将操作栈中计算完成的局部变呈写回局部变量表的存储空间内。


虚拟机栈规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果虚拟机栈可以动态扩展(当前大部分的 Java 虚拟机都可动态扩展),如果扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。


2. 操作栈


操作栈是个初始状态为空的桶式结构栈。在方法执行过程中, 会有各种指令往栈中写入和提取信息。JVM 的执行引擎是基于栈的执行引擎, 其中的栈指的就是操作栈。字节码指令集的定义都是基于栈类型的,栈的深度在方法元信息的 stack 属性中。


我们可以通过 Javap -v 命令,查看每个函数执行的顺序和指令。


如以下这个程序,我们看下 i++和++i 在程序里具体是怎么进行执行的。


public class test {


public static int sum(int i) {


i = i++;


return i;


}


public static int sum2(int i) {


i = ++i;


return i;


}


public static void main(String[] args) {


System.out.println(sum(1));


System.out.println(sum(2));


}


}


将程序使用 javac 变异为 class 文件,然后在执行


javap -v test.class >test


j 将文件导出,然后查看 test 文件内容。大家肯定一脸懵逼。。。。哈哈,不要急,拿出武林秘籍,JAVA指令集,所有的反编译指令都已经总结道那篇博客里了。大家可以当做字典使用。然后我们对照着指令,查看具体执行流程吧。


Last modified Jun 1, 2020; size 549 bytes


MD5 checksum 2aaae1ab4ce8152ade04b4ca65c34a2f


Compiled from "test.java"


public class com.dahua.test.controller.test


minor version: 0


major version: 52


flags: ACC_PUBLIC, ACC_SUPER


Constant pool:


#1 = Methodref #6.#18 // java/lang/Object."<init>":()V


#2 = Fieldref #19.#20 // java/lang/System.out:Ljava/io/PrintStream;


#3 = Methodref #5.#21 // com/dahua/test/controller/test.sum:(I)I


#4 = Methodref #22.#23 // java/io/PrintStream.println:(I)V


#5 = Class #24 // com/dahua/test/controller/test


#6 = Class #25 // java/lang/Object


#7 = Utf8 <init>


#8 = Utf8 ()V


#9 = Utf8 Code


#10 = Utf8 LineNumberTable


#11 = Utf8 sum


#12 = Utf8 (I)I


#13 = Utf8 sum2


#14 = Utf8 main


#15 = Utf8 ([Ljava/lang/String;)V


#16 = Utf8 SourceFile


#17 = Utf8 test.java


#18 = NameAndType #7:#8 // "<init>":()V


#19 = Class #26 // java/lang/System


#20 = NameAndType #27:#28 // out:Ljava/io/PrintStream;


#21 = NameAndType #11:#12 // sum:(I)I


#22 = Class #29 // java/io/PrintStream


#23 = NameAndType #30:#31 // println:(I)V


#24 = Utf8 com/dahua/test/controller/test


#25 = Utf8 java/lang/Object


#26 = Utf8 java/lang/System


#27 = Utf8 out


#28 = Utf8 Ljava/io/PrintStream;


#29 = Utf8 java/io/PrintStream


#30 = Utf8 println


#31 = Utf8 (I)V


{


public com.dahua.test.controller.test();


descriptor: ()V


flags: ACC_PUBLIC


Code:


stack=1, locals=1, args_size=1


0: aload_0


1: invokespecial #1 // Method java/lang/Object."<init>":()V


4: return


LineNumberTable:


line 16: 0


public static int sum(int);


descriptor: (I)I


flags: ACC_PUBLIC, ACC_STATIC


Code:


stack=1, locals=1, args_size=1


0: iload_0 //2、从局部变量 0 中装载 int 类型值 为 1


1: iinc 0, 1 //3、把一个常量值加到一个 int 类型的局部变量上 为 2


4: istore_0 //4、将 int 类型值存入局部变量 0 将 2 存在 0 的位置上


5: iload_0 //5、从局部变量 0 中装载 int 类型值 读取 2


6: ireturn //返回


LineNumberTable:


line 18: 0


line 19: 5


public static int sum2(int);


descriptor: (I)I


flags: ACC_PUBLIC, ACC_STATIC


Code:


stack=1, locals=1, args_size=1


0: iinc 0, 1 //2、把一个常量值加到一个 int 类型的局部变量上


3: iload_0 //3、从局部变量 0 中装载 int 类型值


4: istore_0 //4、将 int 类型值存入局部变量 0


5: iload_0 //5、从局部变量 0 中装载 int 类型值


6: ireturn


LineNumberTable:


line 23: 0


line 24: 5


public static void main(java.lang.String[]);


descriptor: ([Ljava/lang/String;)V


flags: ACC_PUBLIC, ACC_STATIC


Code:


stack=2, locals=1, args_size=1


0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;


3: iconst_1 //1、将 int 类型常量 1 压入栈


4: invokestatic #3 // Method sum:(I)I


7: invokevirtual #4 // Method java/io/PrintStream.println:(I)V


10: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;


13: iconst_2

评论

发布
暂无评论
必知必会JVM二-运行时数据区