JVM内存模型 Java Memory Model(JMM) -- 整理1

JVM内存结构 VS Java内存模型 VS Java对象模型

1.JVM内存模型

在这里插入图片描述

1. 1 PC寄存器(程序计数器)

PC寄存器用来存储指向下一条指令的地址,也即将要执行的指令代码。由执行引擎读取下一条指令。

  • 一块很小的内存空间,几乎可以忽略不记。是运行速度最快的存储区域

  • 每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。

  • 任何时间一个线程都只有一个方法在执行(当前方法)。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址:或者,如果是在执行native方法,则是未指定值(undefned);

  • 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能度需要依赖这个计数器来完成。

  • 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

  • 它是唯一一个在JAVA虚拟机规范中没有规定任何OutOtMemoryError情况的区域;

  • 在这里插入图片描述

  • 1.获取指令地址到PC寄存器(程序计数器);

  • 2.执行引擎通过PC寄存器获取操作指令;

  • 3.执行引擎操作java虚拟机栈和局部变量表;

  • 4.执行引擎将字节码指令翻译成机器指令交给对应的CPU进行运算;

1.2 java虚拟机栈(Java栈):

虚拟机栈也称为Java栈,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧(Stack Frame)。

  • Java虚拟机栈是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)。
  • 栈帧包括局部变量表、操作数栈、动态链接、方法返回地址和一些附加信息。
  • 每一个方法被调用直至执行完毕的过程,就对应这一个栈帧在虚拟机栈中从入栈到出栈的过程。
    在这里插入图片描述
1.2.1栈帧(Stack Frame)

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的java虚拟机栈的栈元素。
  栈帧存储了方法的==局部变量表、操作数栈、动态连接和方法返回地址 锁记录(Lock Record)==等信息。每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

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

1.2.2 局部变量表

是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。 并且在Java编译为Class文件时,就已经确定了该方法所需要分配的局部变量表的最大容量。放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)「String是引用类型」,对象引用(reference类型) 和 returnAddress类型(它指向了一条字节码指令的地址)
注意:
  很多人说:基本数据对象引用存储在栈中。当然这种说法虽然是正确的,但是很不严谨,只能说这种说法针对的是局部变量。局部变量存储在局部变量表中,随着线程而生,线程而灭。并且线程间数据不共享。但是,如果是成员变量,或者定义在方法外对象的引用,它们存储在堆中。因为在堆中,是线程共享数据的,并且栈帧里的命名就已经清楚的划分了界限 : 局部变量表!

  1. 变量槽(Variable Slot)
      局部变量表的容量以变量槽为最小单位,每个变量槽都可以存储32位长度的内存空间,例如boolean、byte、char、short、int、float、reference( 引用)。对于64位长度的数据类型(long,double),虚拟机会以高位对齐方式为其分配两个连续的Slot空间,也就是相当于把一次long和double数据类型读写分割成为两次32位读写。

Slot复用
为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,
也就是说当PC计数器的指令指已经超出了某个变量的作用域(执行完毕),
那这个变量对应的Slot就可以交给其他变量使用。
优点 : 节省栈帧空间。
缺点 : 影响到系统的垃圾收集行为。(如大方法占用较多的Slot,执行完该方法的作用域后没有对Slot赋值或者清空设置null值,垃圾回收器便不能及时的回收该内存。)

1.2.3 操作数栈
  1. 操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
  2. 操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这时方法的操作数栈是空的(这个时候数组是有长度的,只是操作数栈为空)
  3. 每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为maxstack的值。
  4. 栈中的任何一个元素都是可以任意的Java数据类型。
    • 32bit的类型占用一个栈单位深度
    • 64bit的类型占用两个栈单位深度
  5. 操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问。
  6. 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。
  7. 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译器期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。
  8. 另外,我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。

原文链接

1.2.4 reference 引用

在这里插入图片描述

1.2.5动态链接
  • 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用

  • 持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。

  • 在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。

  • 另外的一部分将在每一次运行时期转化为直接引用。这部分称为动态连接。
    在这里插入图片描述

1.2.4方法出口

当一个方法开始执行后,只有2种方式可以退出这个方法 :

  • 方法返回指令 : 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。
  • 异常退出 : 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。
    无论采用任何退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息。
  • 一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。
1.3 本地方法栈 ()

简单来讲,一个Native Method就是一个java调用非java代码的接口。该方法的实现由非java语言实现。在定义一个native method时,并不提供实现体(有些像定义一个java interface),因为其实现体是由非java语言在外面实现的。本地接口的作用是融合不同的编程语言为java所用,它的初衷是融合C/C++程序。
在这里插入图片描述

  • 本地方法栈是一个先进后出(Last In First Out)栈。
  • 由于是线程私有的,生命周期随着线程,线程启动而产生,线程结束而消亡。
  • 本地方法栈会抛出 StackOverflowError 和 OutOfMemoryError 异常。
1.4 java堆()

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。

在 Java 中。堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden(伊甸园)、From Survivor(from 幸存者)、To Survivor(生存)。

  • java堆是线程共享的区域,虚拟机启动时创建
  • 存放对象实例“所有对象和数组都在堆上分配”
  • java堆是垃圾收集器管理的内存区域(也叫GC堆)
  • 堆的大小既可以固定,也可以扩展(通过-Xmx 和 -Xms设定)当超出边界抛出OutOfMemoryError
1.5 方法区 (Non-Heap 非堆)

他用于存储虚拟机已经加载的类型信息常量静态变量即时编译器编译后的代码缓存等数据类型信息

1.5.1保存的信息

对每个加载的类型(class,interface,enum,annotation),JVM必须在方法区中存储以下类型信息:

  • 这个类型的完整有效名称(全名=包名.类名)
  • 这个类型直接父类的完整有效名
  • 这个类型的修饰符(public , abstract , final的某个子集)
  • 这个类型直接接口的一个有序列表

域(Field 字段)信息

  • JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
  • 域的相关信息包括:域名称,域类型,域修饰符

方法(Method)信息

  • JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序
  • 方法名称
  • 方法的返回类型
  • 方法参数的数量和类型
  • 方法的修饰符
  • 方法的字节码,操作数栈,局部变量表及大小(abstract和native方法除外)

异常表

  • 每个异常处理的开始位置,结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引

non-final的类变量

  • 静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分
  • 类变量被类的所有实例共享,即使没有类实例时你也可以访问它

全局常量:static final

  • 被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配

当超出边界抛出OutOfMemoryError

1.6 运行时常量池()

运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法等描述信息外,还有一项是常量池表(Constant Pool Table),用与存放编译器生成的字面量和符号引用。

  • 运行时常量池相比于Class文件常量池的一个重要特性为具备动态性,java不要求长岭一定在编译时期产生,运行期间也可以将新的常量放入常量池,比如 String类的intern()方法

当超出边界抛出OutOfMemoryError

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1999

每人一点点,明天会更好

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值