不可变对象的内存管理

时间:2013-06-06 08:28:10

标签: java memory-management immutability

我对不可变对象的内存管理存在概念上的疑问,例如java和其他语言中的String对象。例如,如果我有一个String对象“str”,其值为“Hello”,我会执行以下操作:

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

在这种情况下,据我所知,创建了一个状态为“Hello World”的新String对象,并将其引用回str。现在,在Java(以及大多数其他面向对象的语言)中,任何对象的生命周期都与其引用一样长。那么持有“你好”的对象去哪里了。它是否驻留在内存堆中,直到垃圾收集器自行处理它?另外,那些不支持垃圾收集器并且必须依赖于类析构函数的语言呢?

此外,如果StringBufferStringBuilder等可变对象更灵活且性能更友好,为什么在设计语言时首先使对象成为不可变的? (我的意思是为什么String Objects从一开始就不可变,而不必在后续的JDK版本中引入新的结构,如String Buffers?)。

如果有人可以指导我这件事会很棒。我是新手,所以一个明确的,基本的解释将受到高度赞赏。感谢。

4 个答案:

答案 0 :(得分:4)

这实际上是一个关于java String类的问题 - 一般而言不是不变量。当Java首次引入时,设计人员决定使String特殊 - 在某些方面它位于引用类型和基本类型之间。

我们使用String获得的优势是虚拟机保留了一个公共的字符串文字池,阻止堆填充 - 请参阅here以获取描述。这背后的原因是程序的大部分内存可以用于存储常用的字符串。另请参阅String.intern

对于任何其他类型的不可变对象,情况并非如此(遗憾的是)。关于str去哪里的问题已由其他人回答 - 它遵循我确定你知道的(或可以找到)的正常垃圾收集规则。

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

  

此外,如果可变对象如StringBuffer / StringBuilder很多   更灵活,更友好,为什么让对象变得可变   首先,在设计语言的同时? (我的意思是为什么不是   字符串对象从一开始就是可变的而不是必须的   在后续的jdk中引入新的结构,如String Buffers   释放?)。

我的回答是,常见的情况是我们有很多相同的字符串,我们希望针对常见情况进行优化。另请注意,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

并获取main方法的以下字节码

  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 :(得分:2)

So where does the object holding "Hello" go

引用“Hello”,str,被赋予新值,因此对值“Hello”的引用会丢失,但它仍然在池中,可用,垃圾收集器可能会收集它并从堆中删除它,它是虽然不确切,但在未来的代码中你仍然使用“Hello”字符串

String againhello= "Hello" ;

然后在这种情况下,垃圾收集器不应该收集它,因为“Hello”字符串已创建并仍然使用,只分配了新的引用。

对象的可变性和不变性背后的概念是,任何两个对象,如果具有相同的值,应该具有相同的哈希码,并且对于equals方法应该返回true,这对于String对象适用,但是为了提高性能

他们将String设置为不可变因为,他们不希望堆中填充相同的值和不同对象的数量,例如,假设

String sre="Hello";

String str="Hello"; 

如果没有String的不变性,那么堆中会有两个对象,但是只有一个对象,这里只有两个引用变量。

what is difference between String and StringBuilder class. 

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

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

String class: 

正如Kathy Siera和Bert Bates在SCJP第433页中所提到的,随着应用程序的增长,在程序的字符串文字中有很多冗余是很常见的。因此,为了解决这个问题,Java的设计者提出了字符串池的概念,它通过有效利用可用内存来提高性能。但是现在,正如您可能想象的那样,如果多个引用变量在不知道的情况下引用相同的String,那么如果它们中的任何一个都可以更改String的值,则会很糟糕。因此,需要使这个String类不可变。

Wrapper classes:

创建包装类的目的之一是提供一种机制来处理具有为对象保留的活动的基元,例如添加到集合,或从具有对象返回值的方法返回。如果您考虑一个集合,通常会出现多个线程访问它的情况。如果包装类不可变,则会遇到并发修改的风险,从而导致状态不一致。因此,为了避免冲突,包装类是不可变的。

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

答案 2 :(得分:2)

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

intfloatchar等原始类型按值复制 - 如果将其值复制到其他位置并编辑其中一个副本,则实际上是全新的已编辑的原语,其他地方未见变化。

字符串不是原语,但从概念上讲,它们在很多方面被“视为原语”。既然我们已经习惯了基元的值特征的复制,那么如果使字符串变得可变,会发生什么呢?但是我们忘记并将它们视为具有按值复制的语义?

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

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

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

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

这是一种权衡 - 在某些方面的性能,与其他方面的性能相比(现在需要复制字符串,只要你想确定你传递字符串的东西不编辑它就是性能损失!),更难关于你的代码的推理(字符串如此无处不在,如果任何字符串随时可能因任何原因发生变化,如果它已被曝光并且获得了另一个引用......)等等。

答案 3 :(得分:1)

你可能在外围地区,Darth Coder和Google不可用,所以这里是Reference Objects and Garbage collection的入门级解释。

关于这在Oracle Java VM中是如何工作的技术解释是here

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