String.intern 解析

基于 JDK 1.8.0_151

intern 是 String.java 中的一个本地方法。
intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串的引用放入常量池中。String字符串作为一种对象也是保存在堆中。

public native String intern();
import java.util.*;
public class TestString {
    public static void main(String[] args) {
        List list = new ArrayList();
        int i = 0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

在JDK 1.6 版本

D:\Java\JDK\jdk1.6_45\bin>javac -version
javac 1.6.0_45

D:\Java\JDK\jdk1.6_45\bin>javac D:\N3verL4nd\Desktop\TestString.java
注意:D:\N3verL4nd\Desktop\TestString.java 使用了未经检查或不安全的操作。
注意:要了解详细信息,请使用 -Xlint:unchecked 重新编译。

D:\Java\JDK\jdk1.6_45\bin>java -XX:MaxPermSize=10M -classpath D:\N3verL4nd\Desktop TestString
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
        at java.lang.String.intern(Native Method)
        at TestString.main(TestString.java:7)

在 JDK 1.7 及以上,此处在 JDK1.8 进行的测试

D:\N3verL4nd\Desktop>javac -version
javac 1.8.0_151

D:\N3verL4nd\Desktop>javac TestString.java
注: TestString.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

D:\N3verL4nd\Desktop>java -Xms10m -Xmx10m TestString
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
        at java.lang.Integer.toString(Integer.java:401)
        at java.lang.String.valueOf(String.java:3099)
        at TestString.main(TestString.java:7)

JDK1.6 字符串常量池在 PermGen space,intern 返回的字符串实例保存在该永生代。
而在JDK 7及以上,字符串常量池保存的仅仅是字符串实例的引用,而字符串实例作为一种对象,还是保存在堆中。以上代码并不能确定字符串常量池存在于哪里。

对HotSpot VM来说,不受GC管理的内存都是native memory;受GC管理的内存叫做GC heap或者managed heap。
“Direct memory”,在Java的上下文里,特指Java程序通过一组特定的API访问native memory。这组API主要由DirectByteBuffer暴露出来。Native memory是一个通用概念,而direct memory只限定在特定的访问native memory的做法。两者不直接等价。
另外JDK7中的HotSpot VM没有把String常量放到native memory,而是把
- interned String => Java heap
- Symbols => native memory

Symbols在JDK7和JDK8里都在native memory里,但不在Metaspace里。
Symbols在native memory里通过引用计数管理,同时有个全局的SymbolTable管理着所有Symbol。

作者:RednaxelaFX
链接:https://www.zhihu.com/question/29352626/answer/44050736
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在 HotSpot 虚拟机中用于管理interned String的那个区域叫做 StringTable
StringTable 其实就是个简单的哈希表,是 HotSpot VM 里用来实现字符串驻留功能的全局数据结构。
如果用Java语法来说,这个 StringTable 其实就是个HashSet<String>——它并不保存驻留String对象本身,而是存储这些被驻留的 String 对象的引用。

VM层面触发的字符串驻留(例如把Class文件里的CONSTANT_String类型常量转换为运行时对象),以及Java代码主动触发的字符串驻留(java.lang.String.intern()),两种请求都由 StringTable 来处理。
一般的,"" 引用的字符串都被标记为 CONSTANT_String_info

HotSpot VM的 StringTable 持有String对象的引用而不是String 对象的本体。被引用的 String 还是在 Java heap 里。一直到JDK6,这些被 intern 的 String 在 permgen 里,JDK7 开始改为放在普通 Java heap 里。


测试 1

String str = "中国人";

使用HSDB调试下
图形界面版:
java -cp .;%JAVA_HOME%/lib/sa-jdi.jar sun.jvm.hotspot.HSDB
命令行版:
java -cp .;%JAVA_HOME%/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB

选择:HSDB–>Tools–>Object Histrgram

这里写图片描述

堆中只有一个内容为”中国人”的 String 实例。
也就是字符串字面量”中国人”所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例。当然字符串常量池保存的只是引用,实例在堆中。

符合规范的JVM实现应该在类加载的过程中创建并驻留一个 String 实例作为常量来对应”中国人”字面量;具体是在类加载的resolve 阶段进行的。这个常量是全局共享的,只在先前尚未有内容相同的字符串驻留过的前提下才需要创建新的 String 实例。

故,str.intern() == str 返回 true。

测试 2

String str = new String("中国人");

这里写图片描述

E:\t00ls\Merry>java -cp .;%JAVA_HOME%/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB
hsdb> attach 5048
Attaching to process 5048, please wait...
hsdb> universe
Heap Parameters:
Gen 0:   eden [0x00000000ff600000,0x00000000ff89e8e8,0x00000000ff8b0000) space capacity = 2818048, 97.46462799781976 used
  from [0x00000000ff8b0000,0x00000000ff8b0000,0x00000000ff900000) space capacity = 327680, 0.0 used
  to   [0x00000000ff900000,0x00000000ff900000,0x00000000ff950000) space capacity = 327680, 0.0 usedInvocations: 0

Gen 1:   old  [0x00000000ff950000,0x00000000ff950000,0x0000000100000000) space capacity = 7012352, 0.0 usedInvocations: 0

hsdb> whatis 0x00000000ff88aba0
Address 0x00000000ff88aba0: In thread-local allocation buffer for thread "main" (3)  [0x00000000ff883040,0x00000000ff88abe8,0x00000000ff890c98,{0x00000000ff890ca8})

hsdb> whatis 0x00000000ff88abb8
Address 0x00000000ff88abb8: In thread-local allocation buffer for thread "main" (3)  [0x00000000ff883040,0x00000000ff88abe8,0x00000000ff890c98,{0x00000000ff890ca8})

可以看到堆中存在两个内容为“中国人”的 String 实例。
一个是字符串字面量”中国人”所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与”中国人”相同的实例

str.intern() == str 返回 false。

另外也可以发现两个 String 实例的元数据都是指向相同的位置。
这里写图片描述

所以在使用上应该优先使用:String str = "";

测试 3

String str = new String("he") + new String("llo");

通过上述分析,我们知道此时会有5个 String 实例。

  • “he”与”llo”在常量池驻留的两个实例。
  • new String("he") 与 new String(“llo”) 创建的两个String 实例。
  • str 对应的实例。

此时 str 也就是”hello”并没有在常量池驻留。
可以通过调用str.intern() 实现此驻留操作。
可以通过以下代码证明”hello”并没有在常量池驻留。

String str = new String("stri") + new String("ngs");
String str1 = "strings";
System.out.println(str.intern() == str);

以上代码返回false,因为此时驻留常量池的”strings”是str1所对应的。
如果拿掉String str1 = "strings"; 这句,那么下一句 str.intern() 把 str 对应的引用驻留在字符串常量池。
这里写图片描述

针对测试三
这里写图片描述
此处有个很有意思的地方,”he”为啥只有一个呢?
因为”he”本身就被加入到常量池了。
好奇的宝宝可以看R大的分析:https://www.zhihu.com/question/51102308/answer/124441115

参考:
http://rednaxelafx.iteye.com/blog/774673/

http://www.iteye.com/topic/1112592

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

N3verL4nd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值