将新字符串创建为 literal/new 对象时的内存分配

Memory allocation when creating new String as literal/new Object

代码:

String s1 = "Hello";
String s2 = new String("Hello");

Field declaredField = String.class.getDeclaredField("value");
declaredField.setAccessible(true);

byte[] arr1 = (byte[])declaredField.get(s1);
byte[] arr2 = (byte[])declaredField.get(s2);

现在,s1 == s2 = False,arr1== arr2 = True。

我的问题是对象是如何存储在内存中的。

第一行确实在池中创建了一个新的 String 类型对象。第二行确实在堆上将其创建为常规对象

但是底层的byte[]数组是一样的。这让我想到,JVM 会以某种方式检查这样一个现有的 byte[] 是否已经存在,并将所有引用指向同一个数组。这是否意味着底层数组缓存在池中,无论我们使用文字还是 new 关键字?

因此 s = "something" 在池中创建了一个新的字符串类型对象以及一个保存数据的底层数组 - 这也是在池中创建的。

s = new String("something") -> 在堆上创建了String类型的对象,但是pool/if中存储的底层数组仍然已经存在,只是创建了一个引用。

我的理解正确吗?

注意:这不是关于常量池存储在哪里、intern 如何工作、创建了多少对象或使用不同的创建方式如何影响 == 结果的问题technique/using intern。

您使用什么 java 版本?我在 11 jdk 上检查它,String.value 的类型是 char[]。所有接下来的单词基于 11 JDK 版本。

此行为的原因是 String 构造函数。

public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

如您所见,value 字段是从 original String 对象复制而来的。由于您使用 "Hello" 作为 new String(...) 参数,因此 "Hello" 从 String 池中获取并传递给构造函数,其中 value 字段正在复制到一个新的 String 对象。

您可以稍微更改代码以达到预期的行为:

String s1 = "Hello";
String s2 = new String("Hell") + "o";
Field declaredField = String.class.getDeclaredField("value");
declaredField.setAccessible(true);
char[] arr1 = (char[])declaredField.get(s1);
char[] arr2 = (char[])declaredField.get(s2);
assert arr1 != arr2;
assert s1.equals(s2);