JVM深入浅出(三) -- String.intern的内存分配和工作机制

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/JacXuan/article/details/70832888

在 Java 语言 , 一些基本变量存放在栈中 , new 出来的变量存放于堆中 , 对于八个基本数据类型而言 , String 这个数据类型比较特殊 , 它可以通过

String str = "string";
String str2 = new String("string");

赋值(这里不考虑 StringBuilder 和 StringBuffer) . 两种不同的赋值方法所得到的在 Java 内存中的存储地址也是截然不同的 .
例如

String str = "string";
String str1 = "str"+"ing";
String str2 = new String("string");
String str3 = new String("string");
System.out.println(str==str1);
System.out.println(str1==str2);
System.out.println(str2==str3);

程序输出

true
false
false

首先,我们要知结果为道Java会确保一个字符串常量只有一个拷贝。
因为例子中的str和str1中的”string”都是字符串常量,它们在编译期就被确定了,所以str==str1为true;而”str”和”ing”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以str1也同样在编译期就被解析为一个字符串常量,所以str1也是常量池中”string”的一个引用。
str2和str3还是常量池中”string”的应用,它们因为无法在编译期确定,所以是运行时创建的新对象”string”的引用 .
然后我们看看 String 的 intern 方法

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern() == t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java™ Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */ 
public native String intern();

方法关键字已经表明了它是一个 native 方法 , 注解中表明了 intern 的作用 – 如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回 .
我们看看一下代码

    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    System.out.println( s3.intern() == s4);

在 jdk 1.6 中得到的输出是 false false ; 而在以后的版本中 得到的是 false true ;
在 jdk7 以后及更高的版本中 ,对于 intern 方法的作用做了调整
JDK6 中的工作原理
图片为引用
在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也没有任何作用 .
jdk7 及以后版本
这里写图片描述
 在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError:PermGen space错误的。在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然jdk8已经直接取消了Perm区域,而新建立了一个元区域(Metaspace)。这意味着不会再有java.lang.OutOfMemoryError: PermGen space问题,也不再需要你进行调优及监控内存空间的使用 .
 在第一段代码中,先看 s3和s4字符串。String s3 = new String(“1”) + new String(“1”);,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap中的 s3引用指向的对象。中间还有2个匿名的new String(“1”)我们不去讨论它们。此时s3引用对象内容是”11″,但此时常量池中是没有 “11”对象的。
接下来s3.intern();这一句代码,是将 s3中的”11”字符串放入String 常量池中,因为此时常量池中不存在”11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个”11”的对象,关键点是 jdk7 中常量池不在Perm区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说引用地址是相同的。
最后String s4 = “11”; 这句代码中”11″是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向s3引用对象的一个引用。所以s4引用就指向和s3一样了。因此最后的比较 s3 == s4 是 true。
再看s和 s2 对象。String s = new String(“1”); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。
接下来String s2 = “1”; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。图中画的很清晰。

来看第二段代码,从上边第二幅图中观察。第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = “11”;后了。这样,首先执行String s4 = “11”;声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。
第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String(“1”);的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。
如果直接用 s3.intern 和 s4 比较 , 那么你将会得到 true
综上所述, jdk7 主要对 intern 操作和常量池做了以下改动
1.将String常量池从Perm区移动到了Java Heap区
2.String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。

展开阅读全文

没有更多推荐了,返回首页