jvm学习之-运行时数据区(runtimedataarea)(代码片段)

TyuIn TyuIn     2023-01-17     701

关键词:

   一、运行时数据区的认识

线程共享和线程私有的区域

名词解释:PC程序计数器    VMS 虚拟机栈  NMS 本地方法栈 

 从上图可以大致的了解到线程私有区域的构成,接下来就对里面的每一个部分展开描述一下:

(1)程序计数器(PC)

        内存空间小,线程私有。字节码解释器工作是就是通过改变这个计数器的值来选取下一条需要执行指令的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。

        程序计数器线程私有的理解:由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。

        如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native 方法,这个计数器值则为空(Undefined)。

        此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError(OOM) 情况的区域。

(2)虚拟机栈(VM Stack)

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

虚拟机栈中可能出现的异常:

  • StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
  • OutOfMemoryError:如果虚拟机栈可以动态扩展,而扩展时无法申请到足够的内存。

1、栈帧(Stack Frame)

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

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

        如上图所示,当前栈桢总是指向栈顶【在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。】执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。

2、局部变量表(Local Variable Table)

        存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。其中64 位长度的long 和double 类型的数据会占用2 个局部变量空间(槽 Slot),其余的数据类型只占用1 个槽。【从这里也反映了一个槽的大小是4字节】局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题。【这当然可以说是废话】

【需要了解什么是编译期和运行期的可以看一下下面这篇博客:Java 编译期与运行期,别傻傻分不清楚! - Java技术栈 - 博客园

        局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁

最后提一些局部变量表中的拓展知识:

 1、Slot(槽)复用

        为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,也就是说当PC计数器的指令指向已经超出了某个变量的作用域(执行完毕),那这个变量对应的Slot就可以交给其他变量使用。【如果当前字节码PC计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。

  • 优点 : 节省栈帧空间。 
  • 缺点 : 影响到系统的垃圾收集行为。(如大方法占用较多的Slot,执行完该方法的作用域后没有对Slot赋值或者清空设置null值,垃圾回收器便不能及时的回收该内存。) ->  注意:垃圾回收是在堆和方法区的,栈中没有垃圾回收
public class A 

    public static void main(String[] args) 
        int a = 0;
        
            int b = 0;
            b = a + 1;
        
        // 变量c使用之前已经销毁的变量b的slot
        int c = a + 1;
    

 2.扩展

        如果当前栈帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。(比如:访问long或double类型变量)【下面是一段简单的测试代码】

public class A 

    public void test()
        int a = 10;
        String b = "hello";
    

3、动态链接(Dynamic Linking)

        每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在每一次运行时期转化为直接引用。这部分称为动态连接。【比如: invokedynamic指令。】

        在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

4、方法返回地址(Return Address)

         方法返回地址(Return Address)(方法正常退出或者异常退出的定义)---- 存放调用该方法的pc寄存器的值。

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

  • ​ 方法返回指令 (return): 执行引擎遇到一个方法返回的字节码指令,这时候有可能会有返回值传递给上层的方法调用者,这种退出方式称为正常完成出口。
  • 异常退出 : 在方法执行过程中遇到了异常,并且没有处理这个异常,就会导致方法退出。

        一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

5、操作数栈(Operand Stack):(表达式栈)

        操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这时这个方法的操作数栈是空的。      

         某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈。比如:执行复制、交换、求和等操作。

    【下面是一个简单的代码讲解以及字节码的具体分析】

public class A 
    /*
         0 bipush 15   操作数 byte 15 入栈
         2 istore_1    操作数出栈,局部变量表索引为 1的位置存储 int 15
         3 bipush 8    操作数入栈 byte 8
         5 istore_2    操作数出栈,局部变量表索引为 2的位置存储 int 8
         6 iload_1     从局部变量表对应索引处获取值,入栈
         7 iload_2     从局部变量表对应索引处获取值,入栈
         8 iadd        15 和 8 出栈求和,再入栈
         9 istore_3    将计算结果存储再局部变量表索引为 3位置 int 23
        10 sipush 800
        13 istore 4
        15 return
     */
    public static void main(String[] args) 
        byte i = 15;
        int j = 8;
        int k = i + j;

        int m = 800;
    

 简单的拓展问题

1、方法中定义的局部变量是否线程安全?

如果只有一个线程可以操作此数据,则必是线程安全的。如果有多个线程操作此数据,则此数据是共享数据。如果不考虑同步机制的话,会存在线程安全问题。

2、分配的栈内存越大越好么?

不是,一定时间内降低了OOM概率,但是会挤占其它的线程空间,因为整个虚拟机的内存空间是有限的。

(3)本地方法栈(Native Method Stack)

        本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native (本地)方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

         【方法区与堆内存在下一篇博客再进行讲解。】本篇主要记录博主在学习java虚拟机过程的笔记以及自己的总结,内容及配图来源《深入理解java虚拟机(第三版)-JVM高级特性与最佳实践 》,书的作者是:周志明老师。文章中的图片主要是尚硅谷JVM课程学习的图片,有兴趣的可以去 blibli 搜索学习。

 

jvm运行时数据区篇(基础认知)(代码片段)

JVM运行时数据区篇1.运行时数据区图2.运行时内存介绍3.运行时分区介绍4.运行时线程介绍1.运行时数据区图2.运行时内存介绍内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时... 查看详情

jvm内存模型(运行时数据区)

运行时数据区(runtimedataarea)jvm定义了几个运行时数据区,这些运行时数据区存储的数据,供开发者的应用或者jvm本身使用。按线程共享与否可以分为线程间共享和线程间独立。线程间独立的运行时数据区线程间独立的区域随线程... 查看详情

图解系列之jvm运行时数据区

查看详情

jvm运行时数据区与jvm堆内存模型小结

前提JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型。那JVM运行时数据区又是什么?它包括:程序计数器、虚拟机栈、本地方法栈、方法区、堆。来看看它们都是干嘛的程序计数器:保存当前线程执行的... 查看详情

jvm运行数据区深度解析(代码片段)

运行数据区字节码只是一个二进制文件存放在那里。要想在jvm里跑起来,先得有个运行的内存环境。也就是我们所说的jvm运行时数据区。1)运行时数据区的位置运行时数据区是jvm中最为重要的部分,执行引擎频繁操作的就是它... 查看详情

jvm专题六:运行时数据区概述

1.运行时数据区架构图2.内存 内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM内存布局规定了JAVA在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行... 查看详情

jvm运行时数据区

...解java虚拟机》上图:这是阅读该章节后画的一个总结图运行时数据区可以分为两部分:线程共享区和线程私有区一、线程共享区这部分区域又分为堆(Heap)和方法区(也就是通常所说的非堆内存)1、Heap(1)堆是在JVM启动时创... 查看详情

jvm学习-运行时数据区2

    上一篇博客我们讲了运行时数据区的一些部分的内容,接下来是运行时数据区中最为重要的两个部分方法区【MethodArea】及堆【Heap】的介绍。一、方法区(Method Area)        方法区(MethodArea)与Java... 查看详情

jvm运行时数据区了解一下?

文章目录​​前言​​​​运行时数据区​​​​程序计数器​​​​虚拟机栈​​​​栈帧​​​​局部变量表​​​​操作数栈​​​​动态链接​​​​返回地址​​​​栈的优化技术——栈帧之间数据的共享​​​​本... 查看详情

jvm-运行时数据区

   JVM在运行Java代码时,会把内存分为几个模块即数据区来使用,数据区的内容如下图所示:1.PC寄存器:  JVM支持程序多线程执行。而操作系统的任务调度采用的是时间片轮询的抢占式调度方式,也就是说,某一个确定... 查看详情

jvm运行时数据区

引自《深入理解Java虚拟机》前言JVM运行时数据分为几大部分程序计数器Java虚拟机栈本地方法栈Java堆方法区(永久代)运行时常量池直接内存JVM内存区域Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的... 查看详情

jvm入门——运行时数据区

...栈(栈)”更是耳熟能详。下面将围绕这张图对JVM的运行时数据区做一个简单介绍。程序计数器(Program Counter Register)  这和计算机操作系统中的程序计数器类似,在计算机操作系统中程序计数器表示这个进程要执 查看详情

jvm(14.3),运行时数据---运行时数据

JVM运行时数据区(JVMRuntimeArea)其实就是指JVM在运行期间,其对计算机内存空间的划分和分配。本文将通过以下几个话题来讨论JVM运行时数据区。 Topic1.JVM运行时数据区里有什么?Topic2.虚拟机栈是什么?虚拟机栈里有什么?Topic3.栈... 查看详情

jvm运行时数据区--堆(代码片段)

一个进程对应一个jvm实例,一个运行时数据区,又包含多个线程,这些线程共享了方法区和堆,每个线程包含了程序计数器、本地方法栈和虚拟机栈。核心概述1.一个jvm实例只存在一个堆内存,堆也是java内存管理的核心区域2.Java... 查看详情

jvm运行时数据区

java虚拟机定义了若干种程序运行时使用到的运行时数据区1.有一些是  随虚拟机的启动而创建,随虚拟机的退出而销毁2.第二种则是与线程一一对应,随线程的开始和结束而创建和销毁。java虚拟机所管理的内存将会包括以... 查看详情

jvm虚拟机------运行时数据区----总体概览

运行时数据区  线程独有本地方法栈、虚拟机栈、程序计数器这些与线程对应的数据区会随着线程开始和结束创建和销毁  JVM涉及的线程线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行在Hotspo... 查看详情

jvm虚拟机------运行时数据区----总体概览

运行时数据区  线程独有本地方法栈、虚拟机栈、程序计数器这些与线程对应的数据区会随着线程开始和结束创建和销毁  JVM涉及的线程线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行在Hotspo... 查看详情

jvm_05运行时数据区2-堆

1.核心概述一个进程对应一个jvm实例,一个运行时数据区,又包含多个线程,这些线程共享了方法区和堆,每个线程包含了程序计数器、本地方法栈和虚拟机栈。一个jvm实例只存在一个堆内存,堆也是java内存管理的核心区域Java... 查看详情