【JVM】Int类型在栈中是否会被缓存?

简介: 【JVM】Int类型在栈中是否会被缓存?

在写面试题系列文章中,多次涉及到JVM的内存分布情况,以及方法执行的过程中局部变量的存储变化情况。比如,在此前已经讲解过字符串常量池的初始化及使用情况。

前些天一位粉丝加微信好友,询问关于int类型的一张存储结构图,主要是对int类型在方法执行的过程中是否存在缓存的情况有疑问。在交流、探讨的过程中收获很多相关知识。本篇文章就汇总分享一下。

int类型的是否会被缓存

首先看下图(其他公众号文章获得),图中显示int类型在栈中会被复用。image.png针对引用类型我们知道栈中只存储引用地址,而对应的值存储在堆中,这没什么问题。而针对int(等基础)类型,变量和值都是存储在栈中(其实也不一定,后面会讲到),那么int类型是否会像字符串常量一样,指向同一个值呢?

用代码展示:

int a = 18;
int b = 18;

变量a和b的值是否是同一个18呢?这便是粉丝提出的疑问,针对此疑问进行了一路探究,逐步获得不少相关知识,下面来逐步讲解。

int类型的入栈指令

要看JVM的操作,查看class文件是必不可少的。而针对int类型,有iconst、bipush、sipush、ldc入栈指令。

当int取值在-1~5时,JVM采用iconst指令将常量压入栈中。如:

int i = 5;
int j = -1;

对应的JVM指令:

Code:
    0: iconst_5
    1: istore_1
    2: iconst_m1
    3: istore_2

int类型的0~5对应JVM的入栈指令分别为iconst_0、iconst_1、iconst_2、iconst_3、iconst_4、iconst_5。当int类型为-1时对应指令为iconst_m1。

当int取值-128~127(一个字节)时,入栈指令均为bipush。

Code:
    0: bipush        127

当int取值-32768~32767(两个字节)时,入栈指令为sipush。

Code:
    0: sipush        32767

当int取值-2147483648~2147483647(三到四个字节)时,入栈指令为ldc。我们知道ldc指令是从常量池进行加载,也就是说当超过2个字节时,int类型会被存储在常量池中。这就是前面说的,为什么int类型不一定都存储在栈中。

经过这一步的分析,我们得知了int类型在JVM操作指令层面的区别,同时也得知即便是int类型,也不一定是变量和值都存储在栈中。

关于int类型在编译器被存储到常量池可以定义一个比较大的int,然后查看class文件便会看到,这里不再举例。

局部变量与操作数栈

在了解局部变量和操作数栈之前,我们先来了解一下栈帧的结构,如下图:image.png栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。方法的调用过程其实就是入栈和出栈的过程。

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

下面看一下class文件的内容,我们便能看到上图中对应的Local Variable Table。image.png上面是通过javap -verbose命令查看class文件中对应的信息。我们可以看到执行的命令部分在Code中,也可以看到局部变量表位于LocalVariableTable部分。

在上图中局部变量表中可以看到Slot一列,其中分别为0、1、2、3。局部变量表的容量以变量槽(Slot)为最小单位。

Slot可以存放boolean、byte、char、short、int、float、reference和returnAddress 8种类型。其中reference 表示对一个对象实例的引用,通过它可以得到对象在Java 堆中存放的起始地址的索引和该数据所属数据类型在方法区的类型信息。

在结构图中,局部变量表下面便是操作栈。那么局部变量表和操作栈是如何协作来完成相应的功能的呢?我们先来看一段相加的代码。

public class IntTest {
  public void test1(){
    int a = 100;
    int b = 98;
    int c = a + b;
  }
}

对应的局部变量表和操作栈的操作如下图:image.png两个int值100和98依次被加载到局部变量表中,计算时先将100、98入栈存放到操作栈中,然后从栈顶依次取出对应的值进行计算,最后再将计算结果存入局部变量表中。

通过javap -verbose命令查看字节码我们再来对照一下上述流程。image.png对照上图中操作字节码的编号和对应局部变量表的变化,能够更加清晰的理解上述过程。

其中局部变量表的第0个Slot明显看到是this当前对象。后面的Slot依次存放了a、b、c三个变量。

总结一下

通过上述的分析,我们基本可以确定针对int类型JVM操作时并不一定会进行缓存处理,只有当int值大小超过2个字节时才会进入常量池。

而最开始粉丝质疑的那张图也的确有问题。虽然没办法直接证明,但只用将98和100换成相同的值,会发现bipush了两次,同时局部变量表中也存了两个变量,从侧面可以证明图的错误。image.png看完本篇文章如果你收获了知识点那么恭喜你,但如果同时发现交流会促进更多思考,挖掘更多新知识,那更应该祝贺你了。


目录
相关文章
|
2月前
|
数据采集 分布式计算 数据处理
Dataphin常见问题之与指定类型int不兼容如何解决
Dataphin是阿里云提供的一站式数据处理服务,旨在帮助企业构建一体化的智能数据处理平台。Dataphin整合了数据建模、数据处理、数据开发、数据服务等多个功能,支持企业更高效地进行数据治理和分析。
|
4天前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
8 0
|
4天前
|
存储 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
10 0
|
4天前
|
存储 安全 Java
Java面试题:在JVM中,堆和栈有什么区别?请详细解释说明,要深入到底层知识
Java面试题:在JVM中,堆和栈有什么区别?请详细解释说明,要深入到底层知识
16 3
|
10天前
|
监控 算法 Java
JVM调优---堆溢出,栈溢出的出现场景以及解决方案
【7月更文挑战第3天】堆溢出(Heap Overflow)和栈溢出(Stack Overflow)是两种常见的内存溢出问题,通常发生在内存管理不当或设计不合理的情况下
12 3
|
16天前
|
存储 缓存 NoSQL
Redis系列学习文章分享---第十三篇(Redis多级缓存--JVM进程缓存+Lua语法)
Redis系列学习文章分享---第十三篇(Redis多级缓存--JVM进程缓存+Lua语法)
32 1
|
20天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
24 2
|
4天前
|
存储 设计模式 监控
Java面试题:简述JVM的内存结构,包括堆、栈、方法区等。栈内存优化的方法有 哪些?
Java面试题:简述JVM的内存结构,包括堆、栈、方法区等。栈内存优化的方法有 哪些?
15 0
|
4天前
|
存储 算法 Java
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
Java面试题:解释JVM的内存结构,并描述堆、栈、方法区在内存结构中的角色和作用,Java中的多线程是如何实现的,Java垃圾回收机制的基本原理,并讨论常见的垃圾回收算法
7 0
|
1月前
|
存储 缓存 安全
JVM(三)-运行时数据区(栈、程序计数器)
JVM(三)-运行时数据区(栈、程序计数器)
16 2