0%

Java堆

本文默认基于64位的JDK 8(其虚拟机实现是HotSpot),除非特别说明。

Java堆内有:Java对象、字符串常量池、包装器类常量池等。

一、Java对象

参见《Java对象》

二、字符串常量池

2.1、概念

字符串常量池,英文名称为“String Literal Pool”或者“String Constant Pool”,几点基本说明:

  • 处于Java堆中,是为了复用String实例对象(在Java语言中,String实例对象不可修改,因此,既能节省内存又不至于引入并发修改等问题)而设计的一种机制所关联的内存区域
  • 如上所述,“字符串常量池”是一种机制,该机制后续可知本质通过几个Java对象实现,契合“Java对象在Java堆内”
  • 在设计之初,“字符串常量池”逻辑上属于“运行时常量池[1]”,而且他们都放置在方法区,比如在JDK 6中;但是现在前者和后者分别放置于Java堆和方法区,虽然“逻辑上属于”仍然成立,但已经名存实亡,意义不大

“字符串常量池”的实现原理如下:

  • 维护一个String实例对象池P,再维护一个映射对象stringTable,可类比为Map<Integer,List<String>>,其“键”为“String实例对象的hashCode值”,其“键值”为“P内具有对应hashCode值的String实例对象的引用值集合”,图示见图1
  • 与“字符串常量池”交互的唯一途径是通过String类的intern()本地方法(实现原理示意见以下源代码1)[2]:
    • 只有String实例对象才能调用String类的intern()本地方法,因此,使用“字符串常量池”机制,虽然最终可以复用String实例对象,但是中间过程中生成“临时的String实例对象”在所难免,这些“临时的String实例对象”最后由于“引用不可达”而被垃圾回收
    • 对每一个字符串字面量(如“hello”,“world”等以直接形式给出的字符串),JVM会创建一个String实例对象,然后自动隐式调用其上的intern()方法

图1

源代码1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class String {

Map<Integer, List<String>> stringTable;

public String intern() {
int hv = this.hashCode();

if (stringTable.get(hv) == null) {
stringTable.put(hv, new ArrayList<String>());
stringTable.get(hv).add(this);
return this;
}

List<String> list = stringTable.get(hv);
for (String s : list) {
if (s.equals(this)) {
return s;
}
}

list.add(this);
return this;
}
}

2.2、实验代码

实验代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.dslztx;

public class StringLiteralPoolExp {

/**
* 运行时,字符串字面量自动隐式调用“intern()”方法
*/
private static String a = "hello";

public static void main(String[] args) {

/**
* 运行时,字符串字面量自动隐式调用“intern()”方法,因此a与b指向同一块内存
*/
String b = "hello";
System.out.println(a == b);

/**
* 运行时,显式调用“intern()”方法,因此a与c指向同一块内存
*/
String c = "hello".intern();
System.out.println(a == c);

/**
* 运行时,显式调用“intern()”方法,因此a与d指向同一块内存
*/
String d = new String("hello").intern();
System.out.println(a == d);

/**
* 运行时,“hello”字符串字面量虽然会自动隐式调用“intern()”方法,但是整体“new String(“hello")”创建一个新的String实例对象,它的内容为“hello”,因此a与e指向不同块内存
*/
String e = new String("hello");
System.out.println(a == e);
}
}

以上代码执行结果如下:

1
2
3
4
true
true
true
false

三、包装器类常量池

3.1、概念

包装器类(包括“Boolean,Byte,Character,Short,Integer,Long”,不包括“Float,Double”)常量池,几点基本说明:

  • 处于Java堆中,是为了复用包装器类实例对象(在Java语言中,包装器类实例对象不可修改,因此,既能节省内存又不至于引入并发修改等问题)而设计的一种机制所关联的内存区域
  • 如上所述,包装器类常量池是一种机制,该机制后续可知本质通过几个Java对象实现,契合“Java对象在Java堆内”

“包装器类常量池”的实现原理如下:

  • 维护一个包装器类实例对象池P,再维护一个映射对象,其“键”为“基本类型值”,其“键值”为“基本类型值对应的包装器类实例对象引用值”
  • 与“包装器类常量池”交互的唯一途径是通过包装器类的valueOf(boolean/byte/char/short/int/long)方法(以Integer为例进行说明,实现原理示意见以下源代码2):
    • 基本类型(boolean,byte,char,short,int,long)的字面量值在转换为对应的包装器类实例时,会自动隐式调用相应包装器类的valueOf(boolean/byte/char/short/int/long)方法

源代码2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import java.util.Properties;

public class Integer {

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

}

3.2、实验代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.dslztx;

public class IntegerLiteralPool {

public static void main(String[] args) {
// 查看“Integer.valueOf(int i)”的源代码
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b);

// 运行时,“100”整型字面量会自动隐式调用“Integer.valueOf(int)”方法,因此a与c指向同一块内存
Integer c = 100;
System.out.println(a == c);

// 运行时,“100”整型字面量虽然会自动隐式调用“Integer.valueOf(int)”方法,但是整体“new Integer(100)”创建一个新的Integer实例对象,它的值为“100”,因此a与d指向不同块内存
Integer d = new Integer(100);
System.out.println(a == d);
}
}

执行结果如下:

1
2
3
true
true
false

四、其他

4.1、JDK 6中的字符串常量池机制和实现

JDK 6中的“字符串常量池机制和实现”跟JDK 8中的“字符串常量池机制和实现”相比有很大不同:

  • 在JDK 8的“字符串常量池机制和实现”中,“字符串常量池”内存区域处于Java堆中;而在JDK 6的“字符串常量池机制和实现”中,“字符串常量池”内存区域处于方法区中,图示见图2
  • 针对intern()方法的实现逻辑,两者的区别:
    • 在JDK 8中,如果“字符串常量池”中不存在一个String实例对象与当前String实例对象A的内容相同,那么将A的引用值Aa加入到“字符串常量池”中的stringTable(相当于A被加入到“字符串常量池”),返回Aa;否则返回“字符串常量池”已存在String实例对象的引用值
    • 在JDK 6中,如果“字符串常量池”中不存在一个String实例对象与当前String实例对象A的内容相同,那么在“字符串常量池”中复制新建(因为分处于“Java堆”和“方法区”)一个String实例对象B,B的内容与A相同,将B的引用值Bb加入到“字符串常量值”中的stringTable,返回Bb;否则返回“字符串常量池”已存在String实例对象的引用值

图2

4.1.1、证明实验1

有如下一段代码,分别使用JDK 6和JDK 8执行:

1
2
3
4
5
6
7
8
9
10
11
public class RuntimeConstantPoolExp {
private static final String a = "java";

public static void main(String[] args) {
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);

String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
}

1、使用JDK 6执行
执行结果如下所示:

1
2
false
false

在第一个判断中,由于“str1”指向Java堆中的String实例对象,而“str1.intern()”指向“字符串常量池”中的String实例对象,故而判断结果为“false”。
在第二个判断中,由于“str2”指向Java堆中的String实例对象,而“str2.intern()”指向“字符串常量池”中的String实例对象,故而判断结果也为“false”。

2、使用JDK 8执行
执行结果如下所示:

1
2
true
false

在第一个判断中,由于“str1”和“str1.intern()”都指向Java堆中的同一个String实例对象,故而判断结果为“true”。
在第二个判断中,由于“str2”指向Java堆中的String实例A,而“str2.intern()”指向Java堆中的另外一个String实例对象B,B指向的String实例对象在“字符串常量池”中。A和B不同,因为“java”字符串字面量已在之前加载“sun.misc.Version”类时被加载——private static final String launcher_name = "java";,B指向彼时生成的String实例对象,故而判断结果为“false”。

4.1.2、证明实验2

另外有如下一段代码,分别使用JDK 6和JDK 8执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.ArrayList;
import java.util.List;

public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
int i = 0;
try {
List<String> list = new ArrayList<String>();
while (true) {
list.add(String.valueOf(i++).intern());
}
} catch (Throwable e) {
System.out.println(e);
} finally {
System.out.println("now i value is: " + i);
}
}
}

1、使用JDK 6执行
执行命令为:

1
2
javac RuntimeConstantPoolOOM.java
java -XX:PermSize=10M -XX:MaxPermSize=10M -Xms50m -Xmx50m RuntimeConstantPoolOOM

执行结果如下所示:

1
2
java.lang.OutOfMemoryError: PermGen space
now i value is: 124018

实验中方法区的内存使用量被设为固定的10M,而“字符串常量池”内存区域属于“方法区”,因此“字符串常量池”内存区域能够使用的内存上限也为10M。因此,当通过String.valueOf(i++).intern()方法不断消耗“字符串常量池”的内存时,最终导致出现“OutOfMemoryError”异常。另外在异常信息中,还提示“PermGen space”,这是因为在JDK 6的HotSpot虚拟机中,使用“永久代”来实现方法区。

2、使用JDK 8执行
执行命令为:

1
2
javac RuntimeConstantPoolOOM.java
java -Xms50m -Xmx50m RuntimeConstantPoolOOM

执行结果如下所示:

1
2
java.lang.OutOfMemoryError: GC overhead limit exceeded
now i value is: 792445

实验中Java堆的内存使用量被设为固定的50M,而“字符串常量池”内存区域属于“Java堆”,因此“字符串常量池”内存区域能够使用的内存上限也为50M。因此,当通过String.valueOf(i++).intern()方法不断消耗“字符串常量池”的内存时,最终导致出现“OutOfMemoryError”异常。
需要注意的是,这里抛出的是“java.lang.OutOfMemoryError: GC overhead limit exceeded”异常,而不是预期的“java.lang.OutOfMemoryError: Java heap space”异常,它们的本质都是“Java堆内存不足”,只不过是两种表现,抛出以上哪种异常并不能被准确预测。[3]

4.1.3、证明实验3

现有如下一段代码,分别使用JDK 6和JDK 8执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
String s1 = new String("hello");
s1.intern();
String s2 = "hello";

System.out.println(s1 == s2);

String s3 = new String("hello") + new String("world");
s3.intern();
String s4 = "helloworld";

System.out.println(s3 == s4);
}
}

1、使用JDK 6执行
执行命令为:

1
2
javac Main.java
java Main

执行结果如下所示:

1
2
false
false

解析说明见下面代码中注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 在JDK 6中,字符串常量池是方法区的一个逻辑区域
*/
public class Main {
public static void main(String[] args) {
// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“hello”,因此在字符串常量池中创建一个String实例对象,它所包含的字符串内容为“hello”,并在stringTable对象中增加对该String实例对象的引用,假定String实例对象引用变量a保存该String实例对象的引用值
// 接着处理“new调用”语句,调用String类构造方法,在Java堆中创建一个String实例对象,String实例对象引用变量s1保存相应的引用值
String s1 = new String("hello");

// 调用“intern()”方法,字符串常量池中已存在String实例对象,它所包含的字符串内容为“hello”(即a引用到的String实例对象),故而没有任何效果
s1.intern();

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象),故而s2=a
String s2 = "hello";

// s1引用到的String实例对象在Java堆中,s2引用到的String实例对象在方法区中,因此,判断结果为“false”
System.out.println(s1 == s2);

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象)
// 接着处理字符串字面量“world”,在Java堆中创建一个String实例对象(包含的字符串内容为“world”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“world”,因此在字符串常量池中创建一个String实例对象,它所包含的字符串内容为“world”,并在stringTable对象中增加对该String实例对象的引用,假定String实例对象引用变量b保存该String实例对象的引用值
// 接着处理两个“new调用”语句,分别调用String类构造方法,在Java堆中创建两个String实例对象
// 最后处理“+”语句,在Java堆中创建一个String实例对象,字符串内容为上述两个String实例对象字符串内容(分别为“hello”和“world”)的拼接,即为“helloworld”,String实例对象引用变量s3保存相应的引用值
String s3 = new String("hello") + new String("world");

// 调用“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“helloworld”,因此在字符串常量池中创建一个String实例对象,它所包含的字符串内容为“helloworld”,并在stringTable对象中增加对该String实例对象的引用,假定String实例对象引用变量c保存该String实例对象的引用值
s3.intern();

// 首先处理字符串字面量“helloworld”,在Java堆中创建一个String实例对象(包含的字符串内容为“helloworld”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“helloworld”(即c引用到的String实例对象),故而s4=c
String s4 = "helloworld";

// s3引用到的String实例对象在Java堆中,s4引用到的String实例对象在方法区中,因此,判断结果为“false”
System.out.println(s3 == s4);
}
}

2、使用JDK 8执行
执行命令为:

1
2
javac Main.java
java Main

执行结果如下所示:

1
2
false
true

解析说明见下面代码中注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 在JDK 8中,字符串常量池是Java堆的一个逻辑区域
*/
public class Main {
public static void main(String[] args) {
// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“hello”,因此在stringTable对象中增加对当前String实例对象的引用,此时,当前String实例对象其实被放入字符串常量池中,假定String实例对象引用变量a保存当前String实例对象的引用值
// 接着处理“new调用”语句,调用String类构造方法,在Java堆中创建一个String实例对象(该String实例对象跟a引用到的String实例对象虽然都处于Java堆中,但是内存地址不同),String实例对象引用变量s1保存相应的引用值
String s1 = new String("hello");

// 调用“intern()”方法,字符串常量池中已存在String实例对象,它所包含的字符串内容为“hello”(即a引用到的String实例对象),故而没有任何效果
s1.intern();

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象),故而s2=a
String s2 = "hello";

// 由上面说明可知,判断结果为“false”
System.out.println(s1 == s2);

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象)
// 接着处理字符串字面量“world”,在Java堆中创建一个String实例对象(包含的字符串内容为“world”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“world”,因此在stringTable对象中增加对当前String实例对象的引用,此时,当前String实例对象其实被放入字符串常量池中,假定String实例对象引用变量b保存当前String实例对象的引用值
// 接着处理两个“new调用”语句,分别调用String类构造方法,在Java堆中创建两个String实例对象
// 最后处理“+”语句,在Java堆中创建一个String实例对象,字符串内容为上述两个String实例对象字符串内容(分别为“hello”和“world”)的拼接,即为“helloworld”,String实例对象引用变量s3保存相应的引用值
String s3 = new String("hello") + new String("world");

// 调用“intern()”方法,字符串常量池中不存在一个String实例对象,它所包含的字符串内容为“helloworld”,因此在stringTable对象中增加对当前String实例对象的引用,此时,当前String实例对象其实被放入字符串常量池中,假定String实例对象引用变量c保存当前String实例对象的引用值,且有c=s3
s3.intern();

// 首先处理字符串字面量“helloworld”,在Java堆中创建一个String实例对象(包含的字符串内容为“helloworld”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“helloworld”(即c引用到的String实例对象),故而s4=c
String s4 = "helloworld";

// 由上面说明可知,判断结果为“true”
System.out.println(s3 == s4);
}
}

4.1.4、证明实验4

现有如下一段代码(它是“4.1.3、证明实验3”中代码的变种),分别使用JDK 6和JDK 8执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = "hello";
s1.intern();

System.out.println(s1 == s2);

String s3 = new String("hello") + new String("world");
String s4 = "helloworld";
s3.intern();

System.out.println(s3 == s4);
}
}

1、使用JDK 6执行
执行命令为:

1
2
javac Main.java
java Main

执行结果如下所示:

1
2
false
false

解析说明见下面代码中注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 在JDK 6中,字符串常量池是方法区的一个逻辑区域
*/
public class Main {
public static void main(String[] args) {
// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“hello”,因此在字符串常量池中创建一个String实例对象,它所包含的字符串内容为“hello”,并在stringTable对象中增加对该String实例对象的引用,假定String实例对象引用变量a保存该String实例对象的引用值
// 接着处理“new调用”语句,调用String类构造方法,在Java堆中创建一个String实例对象,String实例对象引用变量s1保存相应的引用值
String s1 = new String("hello");

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象),故而s2=a
String s2 = "hello";

// 调用“intern()”方法,字符串常量池中已存在String实例对象,它所包含的字符串内容为“hello”(即a引用到的String实例对象),故而没有任何效果
s1.intern();

// s1引用到的String实例对象在Java堆中,s2引用到的String实例对象在方法区中,因此,判断结果为“false”
System.out.println(s1 == s2);

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象)
// 接着处理字符串字面量“world”,在Java堆中创建一个String实例对象(包含的字符串内容为“world”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“world”,因此在字符串常量池中创建一个String实例对象,它所包含的字符串内容为“world”,并在stringTable对象中增加对该String实例对象的引用,假定String实例对象引用变量b保存该String实例对象的引用值
// 接着处理两个“new调用”语句,分别调用String类构造方法,在Java堆中创建两个String实例对象
// 最后处理“+”语句,在Java堆中创建一个String实例对象,字符串内容为上述两个String实例对象字符串内容(分别为“hello”和“world”)的拼接,即为“helloworld”,String实例对象引用变量s3保存相应引用值
String s3 = new String("hello") + new String("world");

// 首先处理字符串字面量“helloworld”,在Java堆中创建一个String实例对象(包含的字符串内容为“helloworld”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“helloworld”,因此在字符串常量池中创建一个String实例对象,它所包含的字符串内容为“helloworld”,并在stringTable对象中增加对该String实例对象的引用,假定String实例对象引用变量c保存该String实例对象的引用值
String s4 = "helloworld";

// 调用“intern()”方法,字符串常量池中已存在String实例对象,它所包含的字符串内容为“helloworld”(即c引用到的String实例对象),故而没有任何效果
s3.intern();

// s3引用到的String实例对象在Java堆中,s4引用到的String实例对象在方法区中,因此,判断结果为“false”
System.out.println(s3 == s4);
}
}

2、使用JDK 8执行
执行命令为:

1
2
javac Main.java
java Main

执行结果如下所示:

1
2
false
false

解析说明见下面代码中注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 在JDK 8中,字符串常量池是Java堆的一个逻辑区域
*/
public class Main {
public static void main(String[] args) {
// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“hello”,因此在stringTable对象中增加对当前String实例对象的引用,此时,当前String实例对象其实被放入字符串常量池中,假定String实例对象引用变量a保存当前String实例对象的引用值
// 接着处理“new调用”语句,调用String类构造方法,在Java堆中创建一个String实例对象(该String实例对象跟a引用到的String实例对象虽然都处于Java堆中,但是内存地址不同),String实例对象引用变量s1保存相应的引用值
String s1 = new String("hello");

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象),故而s2=a
String s2 = "hello";

// 调用“intern()”方法,字符串常量池中已存在String实例对象,它所包含的字符串内容为“hello”(即a引用到的String实例对象),故而没有任何效果
s1.intern();

// 由上面说明可知,判断结果为“false”
System.out.println(s1 == s2);

// 首先处理字符串字面量“hello”,在Java堆中创建一个String实例对象(包含的字符串内容为“hello”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中已存在一个String实例对象,它包含的字符串内容为“hello”(即a引用到的String实例对象)
// 接着处理字符串字面量“world”,在Java堆中创建一个String实例对象(包含的字符串内容为“world”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“world”,因此在stringTable对象中增加对当前String实例对象的引用,此时,当前String实例对象其实被放入字符串常量池中,假定String实例对象引用变量b保存当前String实例对象的引用值
// 接着处理两个“new调用”语句,分别调用String类构造方法,在Java堆中创建两个String实例对象
// 最后处理“+”语句,在Java堆中创建一个String实例对象,字符串内容为上述两个String实例对象字符串内容(分别为“hello”和“world”)的拼接,即为“helloworld”,String实例对象引用变量s3保存相应的引用值
String s3 = new String("hello") + new String("world");

// 首先处理字符串字面量“helloworld”,在Java堆中创建一个String实例对象(包含的字符串内容为“helloworld”),然后自动隐式调用其上的“intern()”方法,发现在字符串常量池中不存在一个String实例对象,它包含的字符串内容也为“helloworld”,因此在stringTable对象中增加对当前String实例对象的引用,此时,当前String实例对象其实被放入字符串常量池中,假定String实例对象引用变量c保存当前String实例对象的引用值
String s4 = "helloworld";

// 调用“intern()”方法,字符串常量池中已存在String实例对象,它所包含的字符串内容为“helloworld”(即c引用到的String实例对象),故而没有任何效果
s3.intern();

// 由上面说明可知,判断结果为“false”
System.out.println(s3 == s4);
}
}

4.2、其他

  1. 本文的很多描述有很多瑕疵,比如“处理字符串字面量的时机是在加载类文件的过程中,而不是在执行相应代码语句的过程中(如有String a=“hello world”代码语句,处理字符串字面量“hello world”的时机是在加载代码语句所在类文件的过程中,而不是在执行该代码语句的过程中)”等。但是,经过分析和实验可以发现,这些瑕疵,对于我们介绍本文的核心内容并没有很大的负面影响
  2. 要想透彻完整了解很多概念,最好的方式还是看JDK的源代码

参考文献

[1]《方法区》
[2]java.lang.String类中public native String intern()方法的JavaDoc
[3]https://blog.csdn.net/renfufei/article/details/77585294
[4]http://openjdk.java.net/jeps/122
[5]http://java-performance.info/string-intern-in-java-6-7-8/
[6]http://tech.meituan.com/in_depth_understanding_string_intern.html
[7]http://blog.csdn.net/u010297957/article/details/50995869
[8]http://droidyue.com/blog/2014/12/21/string-literal-pool-in-java/index.html
[9]https://jimlife.wordpress.com/2007/08/10/java-constant-pool-string/
[10]http://www.javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html
[11]http://theopentutorials.com/tutorials/java/strings/string-literal-pool/
[12]http://www.thejavageek.com/2013/06/19/the-string-constant-pool/
[13]http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/
[14]https://www.hollischuang.com/archives/6569

您的支持将鼓励我继续分享!