2013-06-06 40 views
3

对于不可变对象(如Java和其他语言中的字符串对象)的内存管理,我有一个概念上的疑问。举例来说,如果我有一个String对象“STR”持有价值“你好”,我做到以下几点:不可变对象的内存管理

String str = "Hello"; 
str = str.concatenate("World"); 

在这种情况下,我的理解它与国家的“Hello World”的新String对象被创建并引用回str。现在,在Java(以及大多数其他面向对象的语言中)中,任何对象的生命周期只要其引用处于活动状态即可。那么持有“你好”的对象在哪里呢?它是否驻留在内存堆中,直到垃圾收集器在它自己的闲暇时间处理它?另外,那些不支持垃圾收集器并且不得不依赖于类的析构函数的语言呢?另外,如果诸如StringBufferStringBuilder等可变对象更加灵活且性能友好,为什么在设计语言时首先使对象不可变? (我的意思是为什么String对象从一开始就不可变,而不必在随后的JDK版本中引入新的结构,如字符串缓冲区?)。

如果有人能指导我,这将是一件好事。我对此很陌生,所以我会高度赞赏一个清晰的基本解释。谢谢。

+0

很好的问题! – anshulkatta

+0

不可变对象不具有仅状态值。 – vemv

回答

4

这实际上是一个关于java String类的问题,尤其是一般的不可变性问题。当首次引入Java时,设计人员决定让String变得特别 - 在某些方面,它介于参考类型和原始类型之间。

我们通过String获得的优点是,虚拟机停止堆填满了常用的字符串文字池 - 有关说明,请参阅here。这背后的原因是程序的大部分内存都可以用于存储常用的字符串。另见String.intern

对于任何其他类型的不可变对象,情况并非如此(可悲)。你对str的去向的问题已经被其他人回答 - 它遵循我相信你知道(或可以找出)的正常垃圾收集规则。

也许你的问题中最有趣的部分是

而且,如果可变对象如的StringBuffer/StringBuilder的是多 更加灵活,表现友善,为什么使物体在 可变放在首位,在设计语言? (我的意思是为什么不是 字符串对象从一开始就是不可变的,而不必在 中引入新的结构,如后续jdk 版本中的字符串缓冲区?)。

我的回答将是常见的情况是,我们有很多相同的字符串,我们要优化常见的情况。还要注意,Java编译器在连接字符串时使用StringBuilder。例如借此代码

public class StringBuilderTest { 

    public static void main(String [] args){ 

    String hello = "hello "; 
    String world = "world"; 
    System.out.println(hello+world); 
    } 
} 

和使用

javap的-c StringBuilderTest

拆卸,并得到下面的字节码的主要方法

public static void main(java.lang.String[]); 
    Code: 
     0: ldc   #2     // String hello 
     2: astore_1  
     3: ldc   #3     // String world 
     5: astore_2  
     6: getstatic  #4     // Field java/lang/System.out:Ljava/io/PrintStream; 
     9: new   #5     // class java/lang/StringBuilder 
     12: dup   
     13: invokespecial #6     // Method java/lang/StringBuilder."<init>":()V 
     16: aload_1  
     17: invokevirtual #7     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
     20: aload_2  
     21: invokevirtual #7     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
     24: invokevirtual #8     // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 
     27: invokevirtual #9     // Method java/io/PrintStream.println:(Ljava/lang/String;)V 
     30: return   
} 

它使用一个StringBuilder来执行追加。

1

你可能在外围领域,达斯编码器和谷歌不可用,所以这里是一个入门级的解释Reference Objects and Garbage collection

有关Oracle Java VM如何正确工作的技术说明是here

理解任何语言的垃圾收集的关键思想是可达性。每个对象都需要通过根引用的路径来访问。什么是根引用?示例是方法调用堆栈框架,类,线程,JNI引用等等。从这些根无法到达的所有东西都被认为没有被使用,其空间可以通过文章中描述的方法回收。垃圾收集绝不是微不足道的,也是一个生动的研究领域,所以请耐心等待:-)。

2
So where does the object holding "Hello" go 

所提到的“你好”,海峡,被赋予新的价值,所以参考价值“你好”丢失,但它仍然在池中,可用,垃圾收集器可以收集并从中删除堆,我们不知道确切,让您还在用未来的代码说“你好”字符串

String againhello= "Hello" ; 
在这种情况下

然后,垃圾收集器不应该收集起来,因为,“你好”字符串创建并仍然再次使用,只有新的参考已分配。

的可变性和对象的不变性背后的概念是,任何两个物体是否有相同的价值,应该有相同的hashCode和equals方法应该返回true,则此举行真正的String对象,而是以提高性能

他们成立String作为不可改变的,因为他们不想要堆到充满同样的价值和不同的对象的数量,例如,让说

String sre="Hello"; 

String str="Hello"; 

如果没有字符串的不变性,那么就会出现两个物体在堆中,但只有一个对象,这里只有两个引用变量。

what is difference between String and StringBuilder class. 

StringBuilder类已经在Java 5中添加并在对字符串每次修改,没有新的对象被创建使用StringBuilder的是现在的好处提供的StringBuffer的类似的功能(即..可变的字符串),其相对更快因为StringBuffer是一个同步类,而StringBuilder不是,所以如果你想在不担心线程安全的环境中使用StringBuffer,可以考虑使用StringBuilder来获得更好的性能。

默认情况下,所有Java类都是可变的,即它们的实例的内容可以修改。但是不变性提供的优势很少(http://download.oracle.com/javase/tutorial/essential/concurrency/immutable.html),这就是为什么有些类通过将它们标记为final而成为不可变的原因。有问题的类是String和Wrapper类,如果你从逻辑上思考它们(任何不可变类),那么提供的链接中的描述就会开始有意义。让我们解决各两个分开的:

String class: 

正如凯西Siera和贝尔·贝茨在433 SCJP页提到,作为应用的增长,其很常见的有很多冗余的某个程序的字符串文字。因此,为了解决这个问题,Java的设计者提出了字符串池的概念,通过有效利用可用内存来提高性能。但是现在,正如你可能想象的那样,如果几个引用变量在不知道它的情况下引用相同的字符串,那么如果它们中的任何一个都可以改变字符串的值,那将是不好的。因此,出现了使这个String类不可变的需要。

Wrapper classes: 

一个使包装类的目标是提供一种机制,以治疗与用于对象保留,像被添加到集合,或从与对象返回值的方法返回活动原语。如果你考虑一个集合,通常情况是它被多个线程访问。如果包装类不是可变的,它将面临并发修改的风险,从而导致不一致的状态。因此,为了避免冲突,包装类是不可变的。

因此,一般来说,每当遇到一个不可变类时,想到其并发使用的实例是合乎逻辑的。另外,如果您不希望修改对象内容(其中一个原因是并发访问),那么使该类不可变。

+3

你应该注意到这个答案的重要部分来自这里的一篇文章:http://www.coderanch.com/t/522391/java-programmer-SCJP/certification/String-class-immutable –

2

字符串是不可改变的,遵循最小惊喜的原则。

基本类型,如intfloatchar是按值复制 - 如果它的值复制到另一个地方,然后编辑副本之一,它实际上是一个全新的原始已被编辑,并没有改变被认为在另一个地方。

字符串不是原语,但它们在概念上以很多方式被视为“原语”。由于我们习惯于通过基元的值特性来复制,所以如果字符串变得可变,会发生什么情况,但是我们忘记和处理它们,就好像它们具有按值复制的语义一样?

事情可能会变得混乱。例如:

- 任何时候你返回一个字符串,你必须返回一个字符串的副本,否则字符串的使用者可以编辑它,突然你的字符串也被编辑了!例如,像用户名,密码,消息等数据可能会“令人惊讶地”编辑。

- 安全性是一个问题。如果你调用未知的代码,并且它改变了你正在使用的一个字符串,那么你必须记住并复制所有的字符串(性能问题也是!),或者当它们从脚下变换出来时遭受随机的有害行为。

- 字符串实习是不可能的(这是一个机制,可以重复使用具有相同值的字符串,因此只有一个字符串值的对象存在而不是两个)。字符串内联表依赖于字符串是不可变的。

这是一个权衡 - 在某些方面的性能,与其他方式的性能相比(现在需要复制字符串,只要你想确保你传递字符串的东西不会编辑它就是性能损失!),更多关于你的代码的困难推理(字符串是如此无处不在,并且如果任何字符串可能因任何原因在任何时候改变,如果它已经被暴露并且另一个引用被获取到它)等等。