【Java】JVM学习(二)

JVM的内存区域

运行时数据区域

运行时数据区的定义:

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域

Java 引以为豪的就是它的自动内存管理机制。相比于 C++的手动内存管理、复杂难以理解的指针等,Java 程序写起来就方便的多。

所以要深入理解JVM必须理解内存虚拟化的概念。

在JVM中,JVM 内存主要分为程序计数器方法区虚拟机栈本地方法栈等。
同时按照与线程的关系也可以这么划分区域:

线程私有区域:一个线程拥有单独的一份内存区域。

线程共享区域:被所有线程共享,且只有一份。

这里还有一个直接内存,这个虽然不是运行时数据区的一部分,但是会被频繁使用。你可以理解成没有被虚拟机化的操作系统上的其他内存(比如操作系统上有8G内存,被JVM虚拟化了3G,那么还剩余5G, JVM是借助一些工具使用这5G内存的,这个内存部分称之为直接内存)

在这里插入图片描述

JAVA方法的运行与虚拟机栈

虚拟机栈是用来存放线程运行java方法所需的数据,指令、返回地址。

在这里插入图片描述

这段代码很简单,就是起一个main方法,在main方法运行中调用A方法,A方法中调用B方法,B方法中运行C方法。

我们把代码跑起来,线程1来运行这段代码, 线程1跑起来,就会有一个对应 的虚拟机栈,同时在执行每个方法的时候都会打包成一个栈帧。

比如main开始运行,打包一个栈帧送入到虚拟机栈。

在这里插入图片描述

C方法运行完了,C方法出栈,接着B方法运行完了,B方法出栈、接着A方法运行完了,A方法出栈,最后main方法运行完了,main方法这个栈帧就出栈了。

这个就是Java方法运行对虚拟机栈的一个影响。虚拟机栈就是用来存储线程运行方法中的数据的。而每一个方法对应一个栈帧。

虚拟机栈

栈的数据结构:先进后出(FILO)的数据结构,

虚拟机栈的作用:在JVM运行过程中存储当前线程运行方法所需的数据,指令、返回地址。

虚拟机栈是基于线程的:哪怕你只有一个 main() 方法,也是以线程的方式运行的。在线程的生命周期中,参与计算的数据会频繁地入栈和出栈,栈的生命周期是和线程一样的。

栈帧:在每个 Java 方法被调用的时候,都会创建一个栈帧,并入栈。一旦方法完成相应的调用,则出栈。

栈帧大体都包含四个区域:(局部变量表、操作数栈、动态连接、返回地址)
1、局部变量表:

顾名思义就是局部变量的表,用于存放我们的局部变量的(方法中的变量)。首先它是一个32位的长度,主要存放我们的Java的八大基础数据类型,一般32位就可以存放下,如果是64位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的Object对象,我们只需要存放它的一个引用地址即可。

2、操作数据栈:
存放java方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的java数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的。

操作数栈本质上是JVM执行引擎的一个工作区,也就是方法在执行,才会对操作数栈进行操作,如果代码不不执行,操作数栈其实就是空的。

3、动态连接:
Java语言特性多态。

4、返回地址:
正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)

同时,虚拟机栈这个内存也不是无限大,它有大小限制,默认情况下是1M。
如果我们不断的往虚拟机栈中入栈帧,但是就是不出栈的话,那么这个虚拟机栈就会爆掉。

程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响。

程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。

由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。

因为JVM是虚拟机,内部有完整的指令与执行的一套流程,所以在运行Java方法的时候需要使用程序计数器(记录字节码执行的地址或行号),如果是遇到本地方法(native方法),这个方法不是JVM来具体执行,所以程序计数器不需要记录了,这个是因为在操作系统层面也有一个程序计数器,这个会记录本地代码的执行的地址,所以在执行native方法时,JVM中程序计数器的值为空(Undefined)。
另外程序计数器也是JVM中唯一不会OOM(OutOfMemory)的内存区域。

栈帧执行对内存区域的影响

在这里插入图片描述
在JVM中,基于解释执行的这种方式是基于栈的引擎,这个说的栈,就是操作数栈。

虚拟机栈:

每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口等信息,然后放入栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。

栈的大小缺省为1M,可用参数 –Xss调整大小,例如-Xss256k

在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

局部变量表:

顾名思义就是局部变量的表,用于存放我们的局部变量的。首先它是一个32位的长度,主要存放我们的Java的八大基础数据类型,一般32位就可以存放下,如果是64位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的Object对象,我们只需要存放它的一个引用地址即可。(基本数据类型、对象引用、returnAddress类型)

操作数据栈:

存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的java数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,操作数栈运行方法是会一直运行入栈/出栈的操作

动态连接:

Java语言特性多态(需要类加载、运行时才能确定具体的方法)动态分派

完成出口(返回地址):

正常返回:(调用程序计数器中的地址作为返回)

恢复上层方法的局部变量表和操作数栈、
把返回值(如果有的话)压入调用者栈帧的操作数栈中、
调整程序计数器的值以指向方法调用指令后面的一条指令

异常的话:(通过异常处理表<非栈帧中的>来确定)

猜你喜欢

转载自blog.csdn.net/qq_43358469/article/details/131390659