Java 不可变对象的内存管理

Java 不可变对象的内存管理,java,memory-management,immutability,Java,Memory Management,Immutability,我对java和其他语言中不可变对象(如字符串对象)的内存管理存在概念上的疑问。例如,如果我有一个字符串对象“str”保存值“Hello”,我会执行以下操作: String str = "Hello"; str = str.concatenate("World"); 在本例中,据我所知,将创建一个状态为“Hello World”的新字符串对象,并将其引用回str。现在,在Java(以及大多数其他面向对象语言中)中,任何对象的生命周期都与其引用的生命周期相同。那么,持有“Hello”的对象会去哪里

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

String str = "Hello";
str = str.concatenate("World");
在本例中,据我所知,将创建一个状态为“Hello World”的新字符串对象,并将其引用回str。现在,在Java(以及大多数其他面向对象语言中)中,任何对象的生命周期都与其引用的生命周期相同。那么,持有“Hello”的对象会去哪里呢。在垃圾收集器空闲时处理它之前,它是否一直驻留在内存堆中?另外,对于不支持垃圾收集器且必须依赖于类析构函数的语言又如何呢

另外,如果像
StringBuffer
StringBuilder
这样的可变对象更加灵活和性能友好,那么在设计语言时,为什么首先要使对象不可变呢??(我的意思是为什么字符串对象从一开始就不可变,而不是在后续的JDK版本中引入新的结构,比如字符串缓冲区?)


如果有人能在这方面指导我,那就太好了。我对这一点还不熟悉,所以如果能给出一个清晰、基本的解释,我将不胜感激。谢谢。

您可能在外环地区,达斯编码器和谷歌都不可用,所以这里有一个入门级的解释

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

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

对“Hello”的引用str被分配了新的值,因此对值“Hello”的引用丢失,但它仍然在池中,可用,垃圾收集器可能会收集它并将其从堆中删除,它并不确切,假设在将来的代码中您仍然使用“Hello”字符串

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

对象的易变性和不变性背后的概念是,如果任何两个对象具有相同的值,那么它们应该具有相同的hashcode,并且对于equals方法应该返回true,这对于字符串对象是正确的,但是为了提高性能

他们将字符串设置为不可变,因为他们不希望堆中填充相同的值和不同数量的对象,比如说

String sre="Hello";

String str="Hello"; 
若并没有字符串的不变性,那个么堆中将有两个对象,但这里只有一个对象,只有两个引用变量

what is difference between String and StringBuilder class. 
Java 5中添加了StringBuilder类,并提供了StringBuffer(即可变字符串)的类似功能,在每次修改字符串时,都不会创建新对象。使用StringBuilder的好处是,它比StringBuffer快,因为StringBuffer是一个同步类,而StringBuffer不是,因此,如果您希望在线程安全不受关注的环境中使用StringBuffer,请考虑使用StringBuilder来获得更好的性能。 默认情况下,所有Java类都是可变的,即可以修改其实例的内容。但是不变性提供了一些好处(),这就是为什么一些类通过将它们标记为final而变得不变性。所讨论的类是String和Wrapper类,如果您从逻辑上考虑它们(任何不可变类),那么所提供的链接中的描述将开始有意义。让我们分别讨论这两个问题:

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

Wrapper classes:
制作包装器类的目标之一是提供一种机制,用为对象保留的活动来处理原语,比如添加到集合中,或者从具有对象返回值的方法返回。如果您考虑一个集合,它通常是由多个线程访问的。如果包装类是不可变的,它将面临并发修改的风险,从而导致不一致的状态。因此,为了避免冲突,包装类是不可变的

Wrapper classes:

所以,一般来说,每当你遇到一个不可变的类时,认为它的实例是以并发方式使用是合乎逻辑的。另外,如果您不想修改对象内容(原因之一是并发访问),那么请使类不可变。

这实际上是一个关于java字符串类的问题,而不是一般的不可变性。当Java第一次被引入时,设计者决定让字符串变得特别——在某些方面,它介于引用类型和基本类型之间

我们使用字符串的优点是,虚拟机保留了一个字符串文本的公共池,从而阻止堆被填满—有关描述,请参阅。这背后的原因是,存储常用字符串可以占用程序的大部分内存。看见
Wrapper classes:
public class StringBuilderTest {

  public static void main(String [] args){

    String hello = "hello ";
    String world = "world";
    System.out.println(hello+world);
   }
}
  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        
}