【JVM】Int类型在栈中是否会被缓存?
在写面试题系列文章中,多次涉及到JVM的内存分布情况,以及方法执行的过程中局部变量的存储变化情况。比如,在此前已经讲解过字符串常量池的初始化及使用情况。
前些天一位粉丝加微信好友,询问关于int类型的一张存储结构图,主要是对int类型在方法执行的过程中是否存在缓存的情况有疑问。在交流、探讨的过程中收获很多相关知识。本篇文章就汇总分享一下。
int类型的是否会被缓存
首先看下图(其他公众号文章获得),图中显示int类型在栈中会被复用。
针对引用类型我们知道栈中只存储引用地址,而对应的值存储在堆中,这没什么问题。而针对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文件便会看到,这里不再举例。
局部变量与操作数栈
在了解局部变量和操作数栈之前,我们先来了解一下栈帧的结构,如下图:
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。方法的调用过程其实就是入栈和出栈的过程。
其实在编译代码时,栈帧中需要多大的局部变量表,多深的操作数栈都已经确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。
下面看一下class文件的内容,我们便能看到上图中对应的Local Variable Table。
上面是通过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; } }
对应的局部变量表和操作栈的操作如下图:
两个int值100和98依次被加载到局部变量表中,计算时先将100、98入栈存放到操作栈中,然后从栈顶依次取出对应的值进行计算,最后再将计算结果存入局部变量表中。
通过javap -verbose命令查看字节码我们再来对照一下上述流程。
对照上图中操作字节码的编号和对应局部变量表的变化,能够更加清晰的理解上述过程。
其中局部变量表的第0个Slot明显看到是this当前对象。后面的Slot依次存放了a、b、c三个变量。
总结一下
通过上述的分析,我们基本可以确定针对int类型JVM操作时并不一定会进行缓存处理,只有当int值大小超过2个字节时才会进入常量池。
而最开始粉丝质疑的那张图也的确有问题。虽然没办法直接证明,但只用将98和100换成相同的值,会发现bipush了两次,同时局部变量表中也存了两个变量,从侧面可以证明图的错误。
看完本篇文章如果你收获了知识点那么恭喜你,但如果同时发现交流会促进更多思考,挖掘更多新知识,那更应该祝贺你了。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接