繁体   English   中英

不可变对象的内存管理

[英]Memory management for Immutable Objects

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

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

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

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

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

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

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

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

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

此外,如果可变对象(如StringBuffer / StringBuilder)更灵活且性能更友好,为什么在设计语言时首先使对象变为可变? (我的意思是为什么String Objects从一开始就不是可变的,而不是在后续的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来执行追加。

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:

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

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

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

原始类型(如intfloatchar按值复制 - 如果将其值复制到另一个位置并编辑其中一个副本,它实际上是一个已编辑的全新原语,并且在其他位置看不到任何更改。

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

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

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

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

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

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

您可能在外围地区,Darth Coder和Google不可用,所以这里是参考对象和垃圾收集的入门级​​解释。

这如何准确地在Oracle Java虚拟机的工作原理的技术说明是这里

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM