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

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

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


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


int类型的是否会被缓存

首先看下图(其他公众号文章获得),图中显示int类型在栈中会被复用。


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


用代码展示:


int a = 18;

int b = 18;

1

2

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


int类型的入栈指令

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


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


int i = 5;

int j = -1;

1

2

对应的JVM指令:


Code:

   0: iconst_5

   1: istore_1

   2: iconst_m1

   3: istore_2

1

2

3

4

5

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

1

2

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


Code:

   0: sipush        32767

1

2

当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看完本篇文章如果你收获了知识点那么恭喜你,但如果同时发现交流会促进更多思考,挖掘更多新知识,那更应该祝贺你了。


目录
相关文章
|
6月前
|
存储 Java 索引
32 位和 64 位 JVM 中 int 变量的大小解析
【8月更文挑战第21天】
296 0
|
6月前
|
存储 Java 索引
64 位 JVM 中 int 的大小解析
【8月更文挑战第21天】
82 0
|
存储 缓存 Java
【JVM】Int类型在栈中是否会被缓存?
【JVM】Int类型在栈中是否会被缓存?
142 0
【JVM】Int类型在栈中是否会被缓存?
|
3月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
637 1
|
4月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
55 4
|
2月前
|
存储 Java 程序员
【JVM】——JVM运行机制、类加载机制、内存划分
JVM运行机制,堆栈,程序计数器,元数据区,JVM加载机制,双亲委派模型
|
2月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
3月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
3月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
38 3
|
3月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
73 1