文章目录
- JVM中的字符串常量池
- 串池在不同版本JVM中的内存分布
- intern()方法
- 方法执行效果
- 代码示例
JVM中的字符串常量池
串池在不同版本JVM中的内存分布
在jdk1.6中,字符串常量池和静态变量都位于方法区(HotSpot中称为永久代)中;在jdk7中,字符串常量池和静态变量转移到堆空间中;在jdk8中,新增元空间(MetaSpace,堆共享内存但不相连),去掉了方法区,方法区中的域信息、类信息、方法信息、JIT代码缓存、运行时常量池等移动至元空间。综上所诉,串池在jdk1.6中位于方法区,在jdk7和Jdk8中位于堆空间。
intern()方法
注意:判断串池中是否存在String,是利用equals方法。例如String name = new String("Pioneer") + new String("4");
为了判断串池中是否存在name,底层相当于执行了if ("Pioneer4".equals(name))
。另外,这里没有写String name = new String("Pioneer4");
是因为编译器发现参数中出现“Pioneer4”后,后续就会将“Pioneer4”加入串池中。
方法执行效果
- jdk6
- 如果串池中不存在该String,则将String复制到位于方法区的串池中,返回串池中的String常量的引用。
- 如果出串池中存在该String,则直接返回串池中的String常量的引用。
- jdk7、8、9
- 如果串池中不存在该String,则将指向该String对象的引用复制到串池中,并返回串池中保存的引用。
- 如果串池中存在该String,则直接返回串池中的String常量的应用或者String对象的引用。
代码示例
- jdk6中,串池中不存在“xxx”和存在“xxx”的情况
public class StringPoolTest {
public static void main(String[] args) {
// 串池中不存在"jdk6"
String jdkNew = new StringBuilder("jdk").append("6").toString();
System.out.println(jdkNew == jdkNew.intern());
System.out.println(System.identityHashCode(jdkNew));
System.out.println(System.identityHashCode(jdkNew.intern()));
// 串池中存在”Gosling“
String name = "Gosling";
String nameNew = new StringBuilder("Gos").append("ling").toString();
System.out.println(System.identityHashCode(nameNew));
System.out.println(System.identityHashCode(nameNew.intern()));
System.out.println(nameNew == nameNew.intern());
}
}
输出:
false
1507737389
235262323
536468534
1290300832
false
- jdk7中,串池中不存在”xxx“或String(“xxx”)引用,以及存在”xxx“或String(“xxx”)引用的情况
public class StringPoolTest {
public static void main(String[] args) {
// 串池中不存在"jdk7"
System.out.println("---1---");
String jdkNew = new StringBuilder("jdk").append("7").toString();
System.out.println(jdkNew == jdkNew.intern());
System.out.println(System.identityHashCode(jdkNew));
System.out.println(System.identityHashCode(jdkNew.intern()));
// 串池中存在"Gosling"
System.out.println("---2---");
String name = "Gosling";
String nameNew = new StringBuilder("Gos").append("ling").toString();
System.out.println(System.identityHashCode(nameNew));
System.out.println(System.identityHashCode(nameNew.intern()));
System.out.println(nameNew == nameNew.intern());
System.out.println(name == nameNew.intern());
// 串池中存在String("hobby")的引用
System.out.println("---3---");
String hobby = new StringBuilder("ping").append("pong").toString();
hobby.intern();
String hobbyNew = new String("pingpong");
System.out.println(System.identityHashCode(hobbyNew));
System.out.println(System.identityHashCode(hobbyNew.intern()));
System.out.println(hobbyNew == hobbyNew.intern());
System.out.println(hobby == hobbyNew.intern());
}
}
输出:
---1---
true
1735600054
1735600054
---2---
21685669
2133927002
false
true
---3---
1836019240
325040804
false
true
- 期望在串池中创建数量大致相同的字符串,由于jdk1.6的串池在方法区中,所以会出现OOM;而在jdk7中,可以成功执行
运行参数:-XX:PermSize=10m -XX:MaxPermSize=15m
public class StringPoolSizeTest {
public static void main(String args[]) {
List<String> list = new ArrayList();
for (int i=0; i<Integer.MAX_VALUE; i++) {
list.add(String.valueOf(i).intern());
}
}
}
jdk1.6中方法区内存溢出:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at name.electricalqzhang.concurrencylearn.jvm.base.StringPoolSizeTest.main(StringPoolSizeTest.java:10)
jdk7中正常执行
- jdk7中使用intern()和不使用intern()的内存占用情况:
public class StringPoolGCTest {
public static void main(String args[]) {
Random random = new Random();
List<String> list = new ArrayList();
for (int i=0; i<1000000; i++) {
// 测试执行intern()和不执行intern()的内存占用情况
list.add(String.valueOf(random.nextInt(10)));
// list.add(String.valueOf(random.nextInt(10)).intern());
}
try {
Thread.sleep(1000*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 使用intern(),创建的多数String对象都会被GC回收,占用内存小
- 不使用intern(),导致内存占用大,从JProfiler监控图中可以看到,存活的String实例的数量约等于创建的1000000: