Kum*_*tra 69 java string memory-management
看完SCJP Tip Line的作者Corey McGlone撰写关于javaranch网站的文章后,我感到很困惑.命名为Strings,Literally和SCJP Java 6程序员指南,由Kathy Sierra(javaranch的联合创始人)和Bert Bates撰写.
我将尝试引用Corey先生和Kathy Sierra女士引用的关于String Literal Pool的内容.
1.据Corey McGlone先生说:
String Literal Pool是指向String对象的引用的集合.
String s = "Hello"; (假设Heap上没有名为"Hello"的对象),将"Hello"在堆上创建一个String对象,并在String Literal Pool(Constant Table)中放置对该对象的引用
String a = new String("Bye");(假设Heap上没有名为"Bye" new的对象,操作符将强制JVM在堆上创建一个对象.
现在,"new"对于创建String及其引用的运算符的解释 在本文中有点令人困惑,所以我将文章本身的代码和解释放在下面.
public class ImmutableStrings
{
public static void main(String[] args)
{
String one = "someString";
String two = new String("someString");
System.out.println(one.equals(two));
System.out.println(one == two);
}
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,由于关键字,我们实际上最终会遇到略微不同的行为."new."
在这种情况下,对两个String文字的引用仍然放在常量表(String Literal Pool)中,但是当你来到关键字时"new,"JVM必须在运行时创建一个新的String对象,而不是使用常量表中的那个.
这是解释它的图表..

那么它是否意味着,String Literal Pool也有对这个Object的引用?
以下是Corey McGlone撰写文章的链接
http://www.javaranch.com/journal/200409/Journal200409.jsp#a1
2.据SCJP书中的Kathy Sierra和Bert Bates所说:
为了使Java更具内存效率,JVM预留了一个称为"字符串常量池"的特殊内存区域,当编译器遇到字符串文字时,它会检查池以查看是否已存在相同的字符串.如果没有,则创建一个新的String Literal Object.
String s = "abc"; //创建一个String对象和一个引用变量....
那很好,但后来我对此声明感到困惑:
String s = new String("abc") //创建两个对象和一个引用变量.
它在书中说....正常(非池)内存中的一个新String对象,而"s"将引用它......而另一个文字"abc"将放在池中.
书中的上述内容与Corey McGlone撰写的文章中的内容相冲突.
如果String Literal Pool是Corey McGlone所提到的String对象的引用的集合,那么为什么将文字对象"abc"放在池中(如书中所提到的)?
这个String Literal Pool在哪里?
请清楚这个疑问,虽然在编写代码时无关紧要,但从内存管理方面来说非常重要,这就是我想清除这个基础的原因.
Tom*_*icz 108
我认为这里要理解的主要问题是StringJava对象及其内容之间的区别- char[]在私有value字段下.String基本上是char[]数组的包装器,封装它并使其无法修改,因此String可以保持不可变.另外,String类记住此阵列的部分被实际使用(见下文).这意味着你可以有两个不同的String对象(非常轻量级)指向相同的对象char[].
我会告诉你一些例子,连同hashCode()每一个String和hashCode()内部的char[] value领域(我将其称之为文本从字符串相区别).最后,我将为javap -c -verbose我的测试类显示输出和常量池.请不要将类常量池与字符串文字池混淆.它们并不完全相同.另请参阅了解常量池的javap输出.
为了测试的目的,我创建了一个打破String封装的实用方法:
private int showInternalCharArrayHashCode(String s) {
final Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
return value.get(s).hashCode();
}
Run Code Online (Sandbox Code Playgroud)
这将打印hashCode()的char[] value,有效地帮助我们理解这是否特定String指向相同的char[]文字或没有.
让我们从最简单的例子开始.
String one = "abc";
String two = "abc";
Run Code Online (Sandbox Code Playgroud)
顺便说一句,如果你只是写"ab" + "c",Java编译器将在编译时执行连接,生成的代码将完全相同.这仅在编译时知道所有字符串时才有效.
每个类都有自己的常量池 - 一个常量值列表,如果它们在源代码中出现多次,可以重复使用.它包括常见的字符串,数字,方法名称等.
以下是上面示例中常量池的内容.
const #2 = String #38; // abc
//...
const #38 = Asciz abc;
Run Code Online (Sandbox Code Playgroud)
需要注意的重要事项是字符串指向的String常量object(#2)和Unicode编码的text "abc"(#38)之间的区别.
这是生成的字节码.请注意,两个one和two引用都分配有#2指向"abc"字符串的相同常量:
ldc #2; //String abc
astore_1 //one
ldc #2; //String abc
astore_2 //two
Run Code Online (Sandbox Code Playgroud)
对于每个示例,我打印以下值:
System.out.println(showInternalCharArrayHashCode(one));
System.out.println(showInternalCharArrayHashCode(two));
System.out.println(System.identityHashCode(one));
System.out.println(System.identityHashCode(two));
Run Code Online (Sandbox Code Playgroud)
毫无疑问,这两对是平等的:
23583040
23583040
8918249
8918249
Run Code Online (Sandbox Code Playgroud)
这意味着不仅两个对象都指向相同的char[](下面的文本相同),因此equals()测试将通过.但更多,one并且two是完全相同的参考!所以one == two也是如此.显然,如果one和two指向同一个对象,然后one.value和two.value必须相等.
new String()现在我们都在等待的例子 - 一个字符串文字和一个String使用相同文字的新文字.这将如何运作?
String one = "abc";
String two = new String("abc");
Run Code Online (Sandbox Code Playgroud)
"abc"在源代码中使用常量两次这一事实应该会给你一些提示......
与上述相同.
ldc #2; //String abc
astore_1 //one
new #3; //class java/lang/String
dup
ldc #2; //String abc
invokespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;)V
astore_2 //two
Run Code Online (Sandbox Code Playgroud)
仔细看!第一个对象以与上面相同的方式创建,毫不奇怪.它只需要从常量池中继续引用已创建的String(#2).但是,第二个对象是通过普通的构造函数调用创建的.但!第一个String作为参数传递.这可以反编译为:
String two = new String(one);
Run Code Online (Sandbox Code Playgroud)
输出有点令人惊讶.表示String对象引用的第二对是可以理解的 - 我们创建了两个String对象 - 一个是在常量池中为我们创建的,另一个是为手动创建的two.但是,为什么在地球上第一对表明两个String物体都指向同一个char[] value阵列?!
41771
41771
8388097
16585653
Run Code Online (Sandbox Code Playgroud)
当你看看String(String)构造函数是如何工作的时候会变得很清楚(这里大大简化了):
public String(String original) {
this.offset = original.offset;
this.count = original.count;
this.value = original.value;
}
Run Code Online (Sandbox Code Playgroud)
看到?当您String基于现有对象创建新对象时,它会重复使用 char[] value.Strings是不可变的,不需要复制已知永远不会被修改的数据结构.
我认为这是你的问题的线索:即使你有两个String对象,他们仍然可能指向相同的内容.正如您所看到的,String对象本身非常小.
intern()假设您最初使用了两个不同的字符串,但经过一些修改后它们都是相同的:
String one = "abc";
String two = "?abc".substring(1); //also two = "abc"
Run Code Online (Sandbox Code Playgroud)
Java编译器(至少我的)不够聪明,无法在编译时执行此类操作,看看:
突然间,我们最终得到了两个指向两个不同常量文本的常量字符串:
const #2 = String #44; // abc
const #3 = String #45; // ?abc
const #44 = Asciz abc;
const #45 = Asciz ?abc;
Run Code Online (Sandbox Code Playgroud)
ldc #2; //String abc
astore_1 //one
ldc #3; //String ?abc
iconst_1
invokevirtual #4; //Method String.substring:(I)Ljava/lang/String;
astore_2 //two
Run Code Online (Sandbox Code Playgroud)
第一根弦就像往常一样构造.第二个是通过首先加载常量"?abc"字符串然后调用substring(1)它来创建的.
这里不足为奇 - 我们有两个不同的字符串,指向char[]内存中的两个不同的文本:
27379847
7615385
8388097
16585653
Run Code Online (Sandbox Code Playgroud)
那么,文本并没有真正不同,equals()方法仍然会产生true.我们有两个不必要的同一文本副本.
现在我们应该进行两次练习.首先,尝试运行:
two = two.intern();
Run Code Online (Sandbox Code Playgroud)
在打印哈希码之前.不仅双方one并two指向相同的文字,但它们是相同的参考!
11108810
11108810
15184449
15184449
Run Code Online (Sandbox Code Playgroud)
这意味着两者one.equals(two)和one == two测试都将通过.我们还保存了一些内存,因为"abc"文本只在内存中出现一次(第二个副本将被垃圾回收).
第二个练习略有不同,请查看:
String one = "abc";
String two = "abc".substring(1);
Run Code Online (Sandbox Code Playgroud)
显然,one和two是两个不同的对象,指向两个不同的文本.但是为什么输出表明他们都指向同一个char[]阵列?!?
23583040
23583040
11108810
8918249
Run Code Online (Sandbox Code Playgroud)
我会把答案留给你.它将教你如何substring()工作,这种方法的优点是什么,何时会导致大麻烦.