有关JVM常量池和String.intern() 的一个有趣的问题

今天在JVM群里又碰到猿兄问到如下问题,觉得有点意思,就查看了一下资料,加深自己对JVM的理解,也顺便替猿兄答疑解惑
猿兄问题如下
有关JVM常量池和String.intern() 的一个有趣的问题_第1张图片
一、由于后面两次输出结果较为容易理解,我们先来分析一下后面两次输出的结果
要解决如上问题首先我们需要了解两个知识点:
1、常量池(constant pool)
常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括 字符串 常量,如String s = "java"这种申明方式;当然也可扩充, 执行器 产生的常量也会放入常量池,故认为常量池是 JVM 的一块特殊的内存空间。
有关JVM常量池和String.intern() 的一个有趣的问题_第2张图片
注意:String作为常量和其它float、long、double、int不同的是String是"引用类型",其它是”值类型“
2、String.intern() 
存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个 方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用, 如果没有, 则在常量池中增加一个Unicode等于str的字符串并返回它的引用(这句官网的解释个人觉得有误,应该是“把当前字符串变量的引用添加到常量池”,后面会有分析)
有关JVM常量池和String.intern() 的一个有趣的问题_第3张图片
从官网可以看到intern()是native方法,有空再看看其对应的C++代码吧

下面我还需要举个小例子来解释一下String的两种创建方式的区别
String s="测试3" 创建一个对象“测试3” 放入常量池中
String s=new String("测试3") 创建两个对象,首先new String()的参数是String对象,创建“测试3”放入常量池,然后在new一个对象放入堆中

明白以上概念后,我们来分析后面两次输出结果
有关JVM常量池和String.intern() 的一个有趣的问题_第4张图片
第7行代码解释 在代码编译期间创建了常量"测试2"——>在程序运行期间方法被调用时new StringBuffer(),参数是常量"测试2"---->StringBuffer对象创建完毕后使用toString()转成字符串---->字符串的引用被赋值给s2变量
第8行代码解释 s2.intern()发现常量池中已存在"测试2"的变量并返回它的引用,显然与s2引用不相等

第10代码解释 在代码编译期间创建了常量"测试3"----->在程序运行期间方法被调用时常量"测试3"的引用被赋值给s3变量
第11行代码解释 s3.intern()发现常量池中已存在"测试3"的变量并返回它的引用,与s3比较,相等,因为是同一个引用

二、接下来着重分析一下第一次输出结果的原因
第4行代码解释 在代码编译期间创建了常量"测试"和常量"1"--->在程序运行期间方法被调用时new StringBuffer(),参数是常量"测试"--->通过append方法扩长StringBuffer的大小,参数是常量"1"---->
通过toString()函数把StringBuffer转换为字符串---->字符串的引用被赋值给s1变量
第5行代码解释s1.intern()发现常量池中没有"测试1"常量, intern函数把s1对应的"测试1"的引用添加到常量池中,至此常量池中"测试1"的引用和s1对应的引用相等。

为什么我推测s1.intern()函数是把s1对应的"测试1"的引用添加到常量池中,而不是“在常量池中重新增加一个Unicode等于“测试1”的字符串并返回它的引用”呢?
我们来做个试验便知,对原代码稍作修改如下
有关JVM常量池和String.intern() 的一个有趣的问题_第5张图片
有关JVM常量池和String.intern() 的一个有趣的问题_第6张图片
以args[0]参数为"测试1"进行测试结果如下
有关JVM常量池和String.intern() 的一个有趣的问题_第7张图片
很明显在第6行代码中,args[0].intern()时intern()函数发现常量池中不存在"测试1"常量,便把args[0]对应"测试1"的引用地址添加到常量池中,并返回了其引用地址(个人表达能力不强,不知大家理解没有)
换言之第6行代码和“ System.out.println(s1==args[0])”在此语境下是一个意思;

附件:
下面是猿兄问题类对应的字节码,有助于大家理解常量,可以参详一二
cmd下 javap -v Cl.class有关JVM常量池和String.intern() 的一个有趣的问题_第8张图片

有关JVM常量池和String.intern() 的一个有趣的问题_第9张图片

上图红框中为s1的执行过程为4~5代码对应的class字节码

你可能感兴趣的:(java,JVM,String.intern)