本文默认基于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()
方法
- 只有String实例对象才能调用String类的
图1
源代码1:
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、实验代码
实验代码如下:
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);
}
}
以上代码执行结果如下:
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)
方法
- 基本类型(boolean,byte,char,short,int,long)的字面量值在转换为对应的包装器类实例时,会自动隐式调用相应包装器类的
源代码2:
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、实验代码
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);
}
}
执行结果如下:
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执行:
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执行
执行结果如下所示:
false
false
在第一个判断中,由于“str1”指向Java堆中的String实例对象,而“str1.intern()”指向“字符串常量池”中的String实例对象,故而判断结果为“false”。
在第二个判断中,由于“str2”指向Java堆中的String实例对象,而“str2.intern()”指向“字符串常量池”中的String实例对象,故而判断结果也为“false”。
2、使用JDK 8执行
执行结果如下所示:
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执行:
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执行
执行命令为:
javac RuntimeConstantPoolOOM.java
java -XX:PermSize=10M -XX:MaxPermSize=10M -Xms50m -Xmx50m RuntimeConstantPoolOOM
执行结果如下所示:
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执行
执行命令为:
javac RuntimeConstantPoolOOM.java
java -Xms50m -Xmx50m RuntimeConstantPoolOOM
执行结果如下所示:
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执行:
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执行
执行命令为:
javac Main.java
java Main
执行结果如下所示:
false
false
解析说明见下面代码中注释:
/**
* 在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执行
执行命令为:
javac Main.java
java Main
执行结果如下所示:
false
true
解析说明见下面代码中注释:
/**
* 在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执行:
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执行
执行命令为:
javac Main.java
java Main
执行结果如下所示:
false
false
解析说明见下面代码中注释:
/**
* 在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执行
执行命令为:
javac Main.java
java Main
执行结果如下所示:
false
false
解析说明见下面代码中注释:
/**
* 在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、其他
- 本文的很多描述有很多瑕疵,比如“处理字符串字面量的时机是在加载类文件的过程中,而不是在执行相应代码语句的过程中(如有
String a=“hello world”
代码语句,处理字符串字面量“hello world”的时机是在加载代码语句所在类文件的过程中,而不是在执行该代码语句的过程中)”等。但是,经过分析和实验可以发现,这些瑕疵,对于我们介绍本文的核心内容并没有很大的负面影响 - 要想透彻完整了解很多概念,最好的方式还是看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