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


目录
相关文章
|
存储 Java 索引
32 位和 64 位 JVM 中 int 变量的大小解析
【8月更文挑战第21天】
644 0
|
存储 Java 索引
64 位 JVM 中 int 的大小解析
【8月更文挑战第21天】
221 0
|
存储 缓存 Java
【JVM】Int类型在栈中是否会被缓存?
【JVM】Int类型在栈中是否会被缓存?
170 0
【JVM】Int类型在栈中是否会被缓存?
|
5月前
|
Arthas 存储 算法
深入理解JVM,包含字节码文件,内存结构,垃圾回收,类的声明周期,类加载器
JVM全称是Java Virtual Machine-Java虚拟机JVM作用:本质上是一个运行在计算机上的程序,职责是运行Java字节码文件,编译为机器码交由计算机运行类的生命周期概述:类的生命周期描述了一个类加载,使用,卸载的整个过类的生命周期阶段:类的声明周期主要分为五个阶段:加载->连接->初始化->使用->卸载,其中连接中分为三个小阶段验证->准备->解析类加载器的定义:JVM提供类加载器给Java程序去获取类和接口字节码数据类加载器的作用:类加载器接受字节码文件。
473 55
|
6月前
|
Arthas 监控 Java
Arthas memory(查看 JVM 内存信息)
Arthas memory(查看 JVM 内存信息)
452 6
|
9月前
|
存储 设计模式 监控
快速定位并优化CPU 与 JVM 内存性能瓶颈
本文介绍了 Java 应用常见的 CPU & JVM 内存热点原因及优化思路。
928 166
|
11月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
1796 1
|
7月前
|
存储 缓存 算法
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
325 29
JVM简介—1.Java内存区域
|
7月前
|
缓存 监控 算法
JVM简介—2.垃圾回收器和内存分配策略
本文介绍了Java垃圾回收机制的多个方面,包括垃圾回收概述、对象存活判断、引用类型介绍、垃圾收集算法、垃圾收集器设计、具体垃圾回收器详情、Stop The World现象、内存分配与回收策略、新生代配置演示、内存泄漏和溢出问题以及JDK提供的相关工具。
JVM简介—2.垃圾回收器和内存分配策略
|
7月前
|
存储 设计模式 监控
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?
169 0
如何快速定位并优化CPU 与 JVM 内存性能瓶颈?