JVM的小花招:String Interning

本文详细解析了Java中String对象的生成过程,包括编译期constantpool与运行期runtimeconstantpool的关系,以及StringPool在解决字符串重复问题中的作用。讨论了如何通过使用String str = abc;来优化内存使用,避免不必要的String对象创建。同时,解释了String的intern()方法及其实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

继续了自己研究String相关问题的思路和过程。不敢保证绝对正确,仅仅是一个分析过程。有出错的地方希望大家指出~多交流分享。


问题的出发点

在网上看到一道题:

String str = new String("abc");

问其中生成了多少个String对象?

答案写的是两个。"abc"本身是一个,而new又生成了一个。


探究开始

1.“abc”是什么?似乎不是在heap中的String对象?

我查看了这句代码的bytecode,却发现有些奇怪,如下:

NEW String DUP LDC "abc" INVOKESPECIAL String.<init>(String) : void ASTORE 1

String的构造函数 调用命令实际使用的就是String类型作为参数,那么,栈上应该有一个 String类型的索引。

"abc"并不是直接以object存在的,而是使用LDC命令调用。也就是说,它实际存在于constant pool的索引中。

指令ldc indexbyte 的含义:将两字节的值从indexbyte索引的constant pool中的项中 push到方法栈上。(from Inside the Java2 Virtual machine)。

由此推论,在字节码中,ldc命令在constant pool中找到了能索引到abc那个String对象的索引值。


2.什么是constant pool

翻阅了Inside the JAVA2 virtual machine 和 Programming for the java virtual machine ,仔细查看了这个constant pool的信息。

constant pool是 .class 文件中的一部分,记录了许多constant信息,索引的字符串信息。由于java是动态加载的,.class文件并没有包含程序运行时的内存布局,method的调用等无法直接记录出物理位置,constant pool通过索引的方法解决了这个问题。

Constant Pool中拥有很多不同的表,其中Constant_Utf8_info table中,记录着“string literals that get instantiated as String objects”。

而在String 的java doc中,有对String literal的说明:

All string literals in Java programs, such as"abc", are implemented as instances of this class.

结合这两句话,我们可以理解为,在java编译成.class文件的过程中,确定下来的String literal 都先被优化记录在 constant pool中(那些双引号字符串,都是以CONSTANT_utf8_info的形式存储在constant pool中的)。也就是说,java源代码文件中出现的那些诸如"abc"字符串,都已经被提前放在了constant pool中。

可以参考wiki中对于String interning的资料。


这段代码能证明这一点。

public class Program { public static void main(String[] args) { String str1 = "Hello"; String str2 = "Hello"; System.out.print(str1 == str2); } }


结果是true.
说明"Hello"被程序从同一个内存空间读取出来。


3.constant pool是静态的存在于.class文件的,运行时又怎样?

但是constant pool仅仅是编译时产生的,对于运行时的情况怎么样呢?

Inside the Java2 Virtual Machine 中有看到一句话,"notice that every class has its own run time constant pool".

run time constant pool 其实就是对应于.class文件中constant pool在运行时JVM上的表现形式。


我在String 的java doc中又发现了一个有趣的method:intern(),我翻译如下(翻译的不好,见谅):

当intern方法被调用,如果池中已经拥有一个与该String的字符串值相等(即equals()调用后为true)的String对象时,那么池中的那个String对象会被返回。否则,池中会增加这个对象,并返回当前这个String对象。


其中有介绍一个String pool的东西,

字符串池(String pool),初始是空的,由类Class私有的控制。


到此,我们可以看到,String pool其实是类似于线程池的缓冲器,可以起到节约内存的作用。如下代码可以验证

package biaobiaoqi.thinkingInJava; public class Test { public static void main(String[] args){ String strA1 = "ab"; String strA2 = "c"; String strB1 = "a"; String strB2 = "bc" ; System.out.println((strA1+strA2).intern() == (strB1 + strB2).intern()); } }

结果为true.

至于String Pool到底是怎么样的,暂时没有搞清楚。


4.是否每个class文件的run time constant pool都拥有一个String pool呢?

逻辑上讲,一个JVM拥有一个string pool是比较合理的,因为只有那样,才能尽可能的实现String对象的重用。

我查看了java.lang.String的源代码,发现Intern()方法是一个native方法,即本地实现的方法,而不是一个java方法,这让我们不能直观的看到Strng pool的实现细节。

之后又继续查阅了 关于String Interning方面的知识。发现,原来关于String Intering的那个String pool是在Permanent Generation。

后期的JVM实现里,将heap划分为三部分,permanent gen 是用来存储 Class对象和String pool等的 。

(参见http://en.wikipedia.org/wiki/Java_Virtual_Machine#Heap

说白了,Permanent Generation 就是heap的一部分,只是为了Garbage Collection的方便,将heap按生命周期划分为了young generation 、tenured generation(old generation)和permanent generation( permgen ). 考虑到String pool为了解决字符串的重复问题,生命周期长,就把它放到了permgen




回过头来看看文章刚开始的那个问题。

String str = new String("abc");

这里确实是有两个String对象生成了。但是,要知道String对象的背后还有个String pool在支撑着呢!


整理下思路:

编译java源代码时,源文件中出现的双引号内的字符串都被收纳到 constant pool中,用CONSTANT_utf8_info项存储着。

JVM中,相应的类被加载运行后,constant pool 对应的映射到JVM的runtime constant pool。其中每项CONSTANT_utf8_info(也就试记录那些字符串的)都会在常量引用解析时,自动生成相应的internal String,记录在String pool中,也就试存在于heap中的 PermGen里。

使用String str = new String("xxx")来创建String 对象会在 heap中重新生成新的String对象,绕过String Pool的管辖。而如果使用 String str = "xxx"; 则先查看String Pool 是否已经存在,存在则直接返回PermGen中的该String 对象,否则,生成新的String对象,并将它加入String Pool中。



得出结论:

尽量使用String str = "abc",而不是String str = new String("abc");

用new的方法肯定会开辟新的heap空间,而前者的方法,则会通过string interning优化。

对于intern()这个String 方法好奇的同学,我推荐如下链接

Busting java.lang.String.intern() Myths

讲述了一些对intern方法调用的误解。从下面的评论上看,博主不一定全部正确,但这篇文章确实可以加深对inter机制的认识。



参考资料

What is String literal pool?How to create a String


http://en.wikipedia.org/wiki/Java_Virtual_Machine#Heap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值