原文网址:Java--String--原理--intern方法_IT利刃出鞘的博客-CSDN博客
本文介绍Java的String的intern方法的原理。
在 JAVA 语言中有8种基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池(在方法区)的概念。常量池就类似一个JAVA系统级别提供的缓存。8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。
String的常量池的主要使用方法有两种:
原型:public native String intern();
说明:
返回值:都是返回String变量对应的字符串常量池的引用。
示例
- package com.example;
-
- public class Demo {
- public static void main(String argv[]) {
- String s = "test";
- System.out.println(s == s.intern());
- }
- }
JDK6及之前:输出false
JDK7及之后:输出true
常量池里的字符串的由来
例程1:
- package org.example.a;
-
- public class Demo {
- public static void main(String argv[]) {
- String s1 = new String("1");
- s1.intern();
- String s2 = "1";
- System.out.println(s1 == s2);
-
- String s3 = new String("1") + new String("1");
- s3.intern();
- String s4 = "11";
- System.out.println(s3 == s4);
- }
- }
结果
- jdk6:false false
- jdk7:false true
- jdk8:false true
例程2:
- package org.example.a;
-
- public class Demo {
- public static void main(String argv[]) {
- String s1 = new String("1");
- s1.intern();
- String s2 = "1";
- System.out.println(s1 == s2);
-
- String s3 = new String("1") + new String("1");
- String s4 = "11";
- s3.intern();
- System.out.println(s3 == s4);
- }
- }
上述代码第二部分有个对调。
结果
- jdk6:false false
- jdk7:false false
- jdk8:false false
下边图中:绿色线条代表 String 对象的内容指向。 红色线条代表地址指向。

如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。
在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 会产生java.lang.OutOfMemoryError:PermGen space错误的。在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称jdk8已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA的发展了。字符串常量池移动到JAVA Heap区域,现在解释为什么会有上述的打印结果。


- package org.example.a;
-
- import java.util.Random;
-
- public class Demo {
- static final int MAX = 1000 * 10000;
- static final String[] arr = new String[MAX];
- public static void main(String argv[]) {
- Integer[] DB_DATA = new Integer[10];
- Random random = new Random(10 * 10000);
- for(int i = 0; i < DB_DATA.length; i++){
- DB_DATA[i] = random.nextInt();
- }
-
- long t = System.currentTimeMillis();
- for(int i = 0; i < MAX; i++){
- //arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));
- arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
- }
-
- System.out.println((System.currentTimeMillis() -t) + "ms");
- System.gc();
- }
- }
上述代码是一个演示代码,其中有两条语句不一样,一条是使用 intern,一条是未使用 intern。
运行的参数是:-Xmx2g -Xms2g -Xmn1500M
不用intern
2160ms

使用intern
826ms

通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。
细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后, 然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。