Java string的intern方法讨论
JianLeiXing
2014-02-14
在周志明写的《深入理解Java虚拟机》中,有一个测试case如下:
周志明 写道 package com.jvm.perm; public class RuntimeConstantPoolOOM{ public static void main(String[] args) { String str1 = new StringBuilder("计算机").append("软 件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); } } 在JDK6上运行得到两个false,在JDK7上得到一个true和一个false。 对于JDK7的第二个false的产生有一些疑惑。原文如下: 周志明 写道 对于str2比较返回fasle是因为"java"这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池中已经有他的引用了,不符合“首次出现”原则,而“计算机软件”这个字符首次出现,因此返回true。
如果在后面加上如下代码 ------------------------------------------------------------------ String str3 = new StringBuilder("ja").append("va1").toString(); System.out.println(str3.intern() == str3); ------------------------------------------------------------------ JDK7中打印的是true,这个就更疑惑了。 “计算机软件”和“java”和“java1”这三个字符串有什么不同? 使用javap -v打印的是常量池(Constant Pool Table)信息,而intern方法是操作的运行时常量池,那有什么工具可以查看Java对象的运行时常量池? -以上- |
|
nijiaben
2014-02-14
《通过HSDB来了解String值的真身在哪里》http://lovestblog.cn/2014/02/13/jvm/hsdb_string/
昨天正好写了篇blog描述了类似的问题,可以使用hsdb工具来查看,绝对的利器 |
|
RednaxelaFX
2014-02-15
JianLeiXing 写道 str1.intern() == str1 打捞老帖:http://www.iteye.com/topic/1112592?page=3#2216483 nijiaben 写道 《通过HSDB来了解String值的真身在哪里》http://lovestblog.cn/2014/02/13/jvm/hsdb_string/
昨天正好写了篇blog描述了类似的问题,可以使用hsdb工具来查看,绝对的利器 你的例子里从对象头取klass只能取4字节是因为你开了压缩指针,不是bug:http://rednaxelafx.iteye.com/blog/1010079 |
|
xugangqiang
2014-03-13
牛人们,能不能来点实际的,即这个方法在什么场景下需要使用?
为什么要使用这个方法?用了后有什么好处? |
|
miroku
2014-03-14
RednaxelaFX 写道 JianLeiXing 写道 str1.intern() == str1 打捞老帖:http://www.iteye.com/topic/1112592?page=3#2216483 nijiaben 写道 《通过HSDB来了解String值的真身在哪里》http://lovestblog.cn/2014/02/13/jvm/hsdb_string/
昨天正好写了篇blog描述了类似的问题,可以使用hsdb工具来查看,绝对的利器 你的例子里从对象头取klass只能取4字节是因为你开了压缩指针,不是bug:http://rednaxelafx.iteye.com/blog/1010079 R大能否受累详细的写一片blog总结下string的这些东西,比如string pool,string pool里放的究竟是字符串的那个value数组还是一个指向堆的引用。以及JDK6和JDK7的区别。有图最好了 |
|
RednaxelaFX
2014-03-14
xugangqiang 写道 牛人们,能不能来点实际的,即这个方法在什么场景下需要使用?
为什么要使用这个方法?用了后有什么好处? 简单答案是:别在你的代码里用String.intern()。用它的好处都可以自己用个ConcurrentHashMap来实现。 |
|
xugangqiang
2014-03-14
RednaxelaFX 写道 xugangqiang 写道 牛人们,能不能来点实际的,即这个方法在什么场景下需要使用?
为什么要使用这个方法?用了后有什么好处? 简单答案是:别在你的代码里用String.intern()。用它的好处都可以自己用个ConcurrentHashMap来实现。 能够帮忙一下,描述一个使用的场景? |
|
RednaxelaFX
2014-03-15
xugangqiang 写道 RednaxelaFX 写道 xugangqiang 写道 牛人们,能不能来点实际的,即这个方法在什么场景下需要使用?
为什么要使用这个方法?用了后有什么好处? 简单答案是:别在你的代码里用String.intern()。用它的好处都可以自己用个ConcurrentHashMap来实现。 能够帮忙一下,描述一个使用的场景? 如果您问在应用代码中使用String.intern()的合理场景的话,答案仍然是“不存在”。 不要在您的代码里使用这个方法。 要读背景知识的话,可以先从wiki开始:http://en.wikipedia.org/wiki/String_interning String.intern()的语义是: 假设有两个String的引用s1与s2,若有 s1.equals(s2),则有 s1.intern() == s2.intern()。 也就是说,如果s1所引用的字符串的实际内容与s2所引用的字符串的相等,那么两者都经过intern()之后会得到指向同一字符串实例的引用。 String.intern()最初的用途是去重(消除重复)。如果在某些封装良好的模块里保证其使用的String实例都是经过intern()的,就可以保证这样的模块不会对多个内容相同的字符串实例持有引用,而只会引用一个实例。 进一步说,由于经过intern()的字符串可以保证只要内容相等(equals())就一定引用相等(==),所以可以实现更快速的相等性比较。这对拥有特殊意义的字符串特别有用。这种需求常见于需要跟“标识符”(identifier)打交道的场景,例如说编译器。 ================================ String deduplication for Java-based middleware in virtualized environments http://dl.acm.org/citation.cfm?id=2576210 JEP 192: String Deduplication in G1 http://openjdk.java.net/jeps/192 (未完待续⋯同学们先别回复!) |
|
kennyluck
2014-12-21
引用 (未完待续⋯同学们先别回复!)
求回复啊,这还真不能让 R 大拿一篇老帖打发了,尤其是看到了了《深入解析String#intern》 里的的那个那两段例子的差异。可能有同学觉得没什么,可是我觉得这实在太疯狂了,或许这个例子比较有办法说服你这个行为多可怕: import java.util.Random; class StringIntern { public static void main(String[] args) { Random ran = new Random(); if (ran.nextBoolean()) { System.out.printf("inside if block\n"); String unused = "notinterned"; } String s = new String(new char[] {'n', 'o', 't', 'i', 'n', 't', 'e', 'r', 'n', 'e', 'd'}); s.intern(); System.out.println(s == "notinterned"); } } 结果(我测试用的是我 Mac OS X 10.9.3 自带的 java,java -version 返回 "1.7.0_40")是: lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern true lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern true lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern true lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern inside if block false lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern true lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern true lu-kanghao-no-macbook-pro-2:test kennyluck$ java StringIntern inside if block false 嗯,很有意思,要不要来试试更有意思的: import java.util.Random; class StringIntern { static Random ran = new Random(); public static void main(String[] args) { // 把这里从 10 该成 1000 就可以得到不同的结果了。 for (int i = 0; i < 10; ++i) { optimizeMe(); } String s = new String(new char[] {'n', 'o', 't', 'i', 'n', 't', 'e', 'r', 'n', 'e', 'd'}); s.intern(); System.out.println(s == "notinterned"); } static String optimizeMe() { for (int i = 0; i < 100; ++i) { if (ran.nextLong() > Long.MIN_VALUE) continue; // 如果循环整个被优化掉就不好玩了。 System.out.printf("Bingo! you've got Long.MIN_VALUE"); if ((("notinterned" + ran.nextInt()).length()) > 12) return null; } return null; } } 竟然变成一个循环变量外提侦测器了wwwwww(额,说爽的,多用 -XX:+PrintCompilation 测了一下,看来是只要 optimizeMe 有被 JIT 就会打出 false 的混乱情形了,连里面有没有 "notinterned" 都没差,估计跟 GC 比较有关,求解释!) 回到 R 大在老帖说的: 引用 为什么JRockit与新的JDK7里的HotSpot会返回true其实很简单:它们的string pool并不拷贝输进来intern()的java.lang.String实例,只是在池里记录了每组内容相同的String实例首个被intern的那个实例的引用。
这…… 意思是,先不管 C1/C2,这是代表 ldc 竟然可能会 intern 那个字面量并有改变 string pool 的副作用么!?所以到底是 1) "notinterned" 这个对象在类载入的时候不创建,第一次 ldc 的时候查找 string pool,如果已经有同样的字符串则返回 string pool 原来指向的那个。还是 2) "notinterned" 在类载入的时候创建了,只是在第一次 ldc 的时候才试图加入 string pool,如果已经有同样的字符串则返回 string pool 原来指向的那个。哪一个?好像合乎 Java 规范一些所以猜测是 2),不过求说明。 我觉得这个更动跟驻留字符串在不在 PermGen 是接近平行的两件事啊(也就是我觉得《深入解析String#intern》的那个解释挺奇怪的),第一次 ldc 才加入 string pool 是怕 string pool 有太多 “实际上没有用到的字符串子面量” 造成过多哈希冲突吧?还是求说明。 |