首页 > 编程知识 正文

java堆和栈,java实例变量存放在栈还是堆

时间:2023-05-03 19:52:32 阅读:31208 作者:1916

我们知道虚拟机的内存被划分为多个区域,不是一块大饼。 那么,为什么要分割成多个区域呢? 直接建立区域,使用内存的地方都扔在这个区域不就行了吗? 不痛快吗? 是的,如果不划分区域的话,扔的时候确实很干净。 能用的时候怎么才能再去找呢? 这引入了第一个问题。 分类管理与衣柜、系统盘等类似。 为了便于查找,对分区进行分类。 如果又不进行分区分割,内存没有了怎么办? 在此导入存储器分割的第二个理由是为了容易回收存储器。 没有区别,如果回收内存需要所有的内存扫描,那就晚了。 内存根据使用功能分为不同的区域,因此内存回收也可以根据每个区域的特定情况进行回收。 例如,当堆栈帧随着方法的执行而堆栈时,例如堆栈内存中的堆栈帧,方法的执行将完成并堆栈,但堆内存回收需要使用经典的循环算法进行回收

说到虚拟机的内存结构,您可能首先想到的是堆栈。 对象被分配给堆,堆栈被分配有对对象的引用以及与一些基本数据类型相关的值。 虚拟机的内存结构比这复杂得多。 除了我们知道的(尚未完全识别)堆栈外,还有程序计数器、本地方法堆栈和方法区域。 我们通常称为堆栈内存的是堆栈内存中的局部变量表。

从图中可以看到,有五个大内存区域,根据线程是否共享分为两个部分,一部分是线程独占区域,包括Java堆栈、本地方法堆栈和程序计数器。 部分由线程共享,还包括方法区域和堆。 什么是线程共享和线程独占? 非常能理解。 我们知道,每个Java进程都会同时运行多个线程。 在中,线程共享区域的此区域与所有线程一起使用。 无论有多少个线程,此区域始终是这一个。 每个线程的独占区域都有这样的内存区域,每个线程该区域是唯一的,并且该区域的数量与线程的数量相同。 上图中的空间大小并不表示实际内存空间的大小,还可以在实际运行期间动态调整内存空间的大小。 具体说明各地区的主要功能。

程序计数器,在我们编写代码的过程中,开发工具一般会进行行号查看和阅读代码。 在中,有一个行号,即使程序正在运行,该行号也有助于虚拟机的运行。 是程序计数器。 在c语言中,我们知道有goto语句,但实际上它会跳到指定的行。 这个行号就是程序计数器。 保存的是程序下一次执行的指令。 我们知道这个部分区域是线程独占共享的区域,线程是顺序执行流,每个线程都有自己的执行顺序,如果所有线程共享一个程序计数器,程序执行就会变得混乱。 为了保证每个线程的执行顺序,程序计数器被单个线程独占。 名为程序计数器的内存空间是唯一没有在jvm规范中规定内存溢出的内存空间。

Javavirtualmachinestack、Javavirtualmachinestack是程序执行的动态区域,各方法的执行伴随着堆栈帧的堆栈和外堆栈。 堆栈帧(也称为流程活动日志)是编译器用于实现过程/函数调用的数据结构。 堆栈框架包含局部变量表、操作数堆栈、方法返回地址和其他信息。 在编译期间,局部变量表的大小将被确定,操作数堆栈的深度也将被确定,因此堆栈帧在运行时分配的内存量将被固定,而与运行时无关。 堆栈中还分配了内存给未转义的对象,对象的大小实际上也是在运行时确定的,因此即使发生堆栈上的内存分配,堆栈帧的大小也不会发生变化。

在一个线程中,调用链变长,许多方法可能同时执行。 对执行引擎来说,在活动线程中,只有堆栈顶部的堆栈帧效果最好,称为当前堆栈帧,与此堆栈帧关联的方法称为当前方法。 执行引擎执行的字节码指令只对当前堆栈帧运行。 ft5rk 58 gfijxcdczgeat8fjkfpkmrdf

局部变量表:通常堆栈存储器是指堆栈存储器内的局部变量表。 这里主要用于存储变量。 基本数据类型直接存储值,引用数据类型存储地址。 局部变量表的最小存储单位是插槽,每个插槽可以包含布尔、字节、字符、短整型、整型、浮点型、引用或返回地址类型的数据。

因为前面提到了数据类型,所以这里一个Slot可以存储32位以内的数据类型。 在Java中占32位以内的数据类型有8种:布尔、字节、字符、短整型、浮点型、引用和返回地址。 前面的六种不用多解释,大家都知道,后面的参考是对象的引用。 虚拟机规格没有指明其长度,也没有明确说明此引用应如何配置。 但是,VM实现通常至少需要通过此引用直接或间接找到Java堆中对象的起始地址索引和方法区域中的对象类型数据。 returnAddress服务于字节码指令jsr、jsr_w和ret,并指向字节码指令的地址。

对于64位数据类型,虚拟机从顶部开始按顺序分配两个连续的插槽空间。 Java语言中明确规定的64位数据类型只有long和double两种。 参考类型可以是32位或64位。 有趣的是,这里将long和double数据类型的读写分割为2次32读写的做法相似。 但是,由于局部变量表构建在线程的堆栈上,并且是线程的专用数据,因此无论读写连续的两个Slot是否为原子操作,都不会引起数据安全问题。

操作数堆栈是Lifo(lastinfirstout,LIFO )堆栈。 与局部变量表一样,操作数堆栈的最大深度也在编译时写入字节码文件,并关于字节码文件

,后面我会具体的来描述。操作数栈的每一个元素可以是任意的Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。在方法执行的任何时候,操作数栈的深度都不会超过在max_stacks数据项中设定的最大值。

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。例如,在做算术运算的时候是通过操作数栈来进行的,又或者在调用其他方法的时候是通过操作数栈来进行参数传递的。

举个例子,整数加法的字节码指令iadd在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会将这两个int值和并相加,然后将相加的结果入栈。

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,在编译程序代码的时候,编译器要严格保证这一点,在类校验阶段的数据流分析中还要再次验证这一点。再以上面的iadd指令为例,这个指令用于整型数加法,它在执行时,最接近栈顶的两个元素的数据类型必须为int型,不能出现一个long和一个float使用iadd命令相加的情况。

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

方法区经常会被人称之为永久代,但这俩并不是一个概念。首先永久代的概念仅仅在HotSpot虚拟机中存在,不幸的是,在jdk8中,Hotspot去掉了永久代这一说法,使用了Native Memory,也就是Metaspace空间。那么方法区是干嘛的呢?我们可以这么理解,我们要运行Java代码,首先需要编译,然后才能运行。在运行的过程中,我们知道首先需要加载字节码文件。也就是说要把字节码文件加载到内存中。好了,问题就来了,字节码文件放到内存中的什么地方呢,就是方法区中。当然除了编译后的字节码之外,方法区中还会存放常量,静态变量以及及时编译器编译后的代码等数据。

堆,一般来讲堆内存是Java虚拟机中最大的一块内存区域,同方法区一样,是被所有线程所共享的区域。此区域所存在的唯一目的就存放对象的实例(对象实例并不一定全部在堆中创建)。堆内存是垃圾收集器主要光顾的区域,一般来讲根据使用的垃圾收集器的不同,堆中还会划分为一些区域,比如鲤鱼铃铛代和老年代。鲤鱼铃铛代还可以再划分为dmdlz,Survivor等区域。另外为了性能和安全性的角度,在堆中还会为线程划分单独的区域,称之为线程分配缓冲区。更细致的划分是为了让垃圾收集器能够更高效的工作,提高垃圾收集的效率。

如果想要了解更多的关于虚拟机的内容,可以观看录制的这套视频教程。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。