不可变对象的java内存管理

aydmsdu9  于 2021-06-30  发布在  Java
关注(0)|答案(4)|浏览(393)

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

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

在本例中,据我所知,创建了一个状态为“hello world”的新string对象,并将其引用回str。现在,在java(以及大多数其他面向对象语言)中,任何对象的生命周期都是只要其引用是活动的。那么,持有“hello”的物体会去哪里呢。在垃圾收集器空闲时处理它之前,它是否一直驻留在内存堆中?另外,那些不支持垃圾收集器并且必须依赖于类析构函数的语言呢?
另外,如果可变对象 StringBuffer 以及 StringBuilder 更灵活和更友好的性能,为什么在设计语言时首先要使对象不变(我的意思是为什么字符串对象不能从一开始就可变,而不是在随后的jdk版本中引入新的结构,比如字符串缓冲区。
如果有人能在这方面指导我就太好了。我是新来的,所以一个明确的,基本的解释将高度赞赏。谢谢。

3phpmpom

3phpmpom1#

So where does the object holding "Hello" go

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

String againhello= "Hello" ;

在这种情况下,垃圾收集器不应该收集它,因为创建了“hello”字符串,并且仍然使用它,只分配了新的引用。
对象的可变性和不变性背后的概念是,任何两个对象如果具有相同的值,就应该具有相同的hashcode,并且对于equals方法应该返回true,这对于string对象是正确的,但是为了提高性能
他们将字符串设置为不可变的,因为他们不希望堆中填充相同的值和不同对象的数目,比如说

String sre="Hello";

String str="Hello";

如果字符串没有不变性,那么堆中将有两个对象,但这里只有一个对象,只有两个引用变量。

what is difference between String and StringBuilder class.

Java5中添加了stringbuilder类,并提供了与stringbuffer类似的功能。。可变字符串)每次对字符串进行修改时,都不会创建新对象。使用stringbuilder的好处是,它比stringbuffer的速度要快,因为stringbuffer是一个同步类,而stringbuilder不是,所以如果您想在不考虑线程安全的环境中使用stringbuffer,考虑使用stringbuilder以获得更好的性能。
默认情况下,所有java类都是可变的,也就是说,可以修改它们示例的内容。但是不变性提供的好处很少(http://download.oracle.com/javase/tutorial/essential/concurrency/immutable.html),这就是为什么有些类通过将它们标记为final而变得不可变。所讨论的类是字符串类和 Package 类,如果您从逻辑上考虑它们(任何不可变的类),那么所提供的链接中的描述将开始有意义。让我们分别讨论这两个问题:

String class:

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

Wrapper classes:

制作 Package 器类的目标之一是提供一种机制来处理具有为对象保留的活动的原语,如添加到集合中,或从具有对象返回值的方法返回的活动。如果您考虑一个集合,通常情况下它是由多个线程访问的。如果 Package 器类是不可变的,它将面临并发修改的风险,从而导致不一致的状态。因此,为了避免冲突, Package 类是不可变的。
因此,一般来说,每当您遇到一个不可变的类时,将其示例以并发方式使用是合乎逻辑的。另外,如果不希望修改对象内容(其中一个原因是并发访问),请使类不可变。

rkttyhzu

rkttyhzu2#

你可能在外环地区,达斯编码器和谷歌是不可用的,所以这里有一个参考对象和垃圾收集入门级的解释。
关于这在oraclejavavm中究竟是如何工作的技术解释如下。
理解任何语言中的垃圾回收的关键思想是可达性。每个对象都需要通过根引用的路径来访问。什么是根引用?例如方法调用堆栈框架链、类、线程、jni引用等。从这些根不能到达的所有东西都被认为没有被使用,它的空间被文章中描述的方法回收。垃圾收集绝不是一个琐碎和生动的研究领域,所以要耐心:-)。

vlju58qv

vlju58qv3#

这实际上是一个关于java字符串类的问题,而不是一般的不变性。当java第一次被引入时,设计者决定让字符串变得特别——在某些方面,它介于引用类型和基本类型之间。
使用string的好处是,虚拟机保留了一个字符串文本的公共池,从而阻止堆被填满—请参阅此处以获取描述。这背后的原因是,存储常用字符串可以占用程序的大部分内存。另请参见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-cstringbuilder测试
并为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执行追加。

wrrgggsh

wrrgggsh4#

字符串是不变的,以遵循最小意外的原则。
基本类型,例如 int , float 以及 char 按值复制-如果将其值复制到另一个位置并编辑其中一个副本,则它实际上是一个已编辑的全新基本体,在另一个位置看不到任何更改。
字符串不是原语,但从概念上讲,它们在很多方面被视为“原语”。既然我们已经习惯了原语的copy-by-value特性,那么如果字符串被设置为可变的,但我们忘记了它们,并将其视为具有copy-by-value语义,会发生什么呢?
事情可能会变得一团糟。例如:
-任何时候你返回一个字符串,你必须返回一个字符串的副本,否则字符串的使用者可以编辑它,突然你的字符串也被编辑了!例如,用户名、密码、消息等数据可能会被“意外”编辑。
-安全是个问题。如果您调用未知代码并更改了您正在使用的字符串,那么您必须记住并复制所有字符串(性能问题也是如此!)或遭受随机有害的行为,当他们从你的脚下改变。
-字符串interning是不可能的(这是一种机制,通过这种机制,具有相同值的字符串可以被重用地分发,因此只有一个字符串值的对象存在,而不是两个)。字符串intern表依赖于不可变的字符串。
这是一种折衷——在某些方面是性能,而在其他方面是性能(现在需要在任何时候复制字符串,以确保传递字符串的对象不被编辑,这是一种性能损失!),关于代码的推理更加困难(字符串是如此普遍,如果任何字符串在任何时候由于任何原因而改变,如果它曾经被公开并且获得了另一个对它的引用……),等等。

相关问题