【Java必修课】String.intern()原来还能这么用(原理与应用)

1 简介

String.intern()是JDK一早就提供的native方法,不禁Java实现,而是底层JVM实现,这让咱们对它的窥探提升了难度。特别是在Oracle收购了Sun公司后,源代码不开源了,更没法深刻研究了。但咱们仍是有必要尽可能地去探索。java

本文将主要讲解一下String.intern()方法的原理、特色,并介绍一个新奇的应用。app

2 String的池化

方法intern()的做用就是将String池化,这个池是String的常量池。不一样版本的JDK有不一样的实现。ui

2.1 不一样实现与不一样内存空间

  • JDK 6:intern()方法会把首先遇到的字符串复制一份到永久代中,而后返回永久中的实例引用;若是不是首次,说明常量池中已经有该字符串,直接返回池中的引用。常量池在永久代(PermGen)中。
  • JDK 7:intern()方法首次遇到字符串时,不会复制实例,而是直接把该字符串的引用记录在常量池中,并返回该引用;若是不是首次,则直接返回池中引用。JDK 7常量池在堆中。
  • JDK 8:功能与JDK 7相似。常量池在元空间Metaspace中,元空间不在虚拟机内存中,而是使用本地内存。

2.2 常量池大小差别

这个所谓的String常量池,其实就是一张哈希表,跟HashMap相似,因此也是有大小限制和哈希冲突可能。常量池越大,哈希冲突可能性越小。this

  • JDK 6早期版本,池大小为常量1009,后期变得可配置,经过参数-XX:StringTableSize=N指定。大小也会受限于永久代的大小,建议避免使用intern()方法,防止形成PermGen内存溢出。spa

  • JDK 7将常量池移到堆后,能够存放更多常量,也同样经过参数可配置大小。在Java 7u40后,常量池默认大小增长到了60013。线程

  • JDK 8默认大小一开始就是60013,依旧支持参数配置。code

总的来讲,-XX:StringTableSize的默认值在Java 7u40之前为1009,Java 7u40之后改成60013。对象

3 例子分析

经过例子,来理解一下就更清晰了。JDK 7和8应该表现一致,本文使用JDK 8。内存

3.1 JDK 8

先演示JDK 8的状况:ci

例子1

String str1 = new String("pkslow");
System.out.println(str1.intern() == str1);

结果:false

分析:由于使用了字面量,在编译期就会把字符串放到常量池,当使用new String()时,会建立新的对象。因此常量池中的引用与建立的对象引用不一样。

例子2

String str1 ="pkslow";
System.out.println(str1.intern() == str1);

结果:true

分析:与上个例子对比,将常量池的地址赋值给了str1变量,因此相等。

例子3

String str1 = new StringBuilder("pk").append("slow").toString();
System.out.println(str1.intern() == str1);

String str2 = new StringBuilder("pk").append("slow").toString();
System.out.println(str2.intern() == str2);

结果:true false

分析:

(1)第一句建立了一个新的字符串对象,str1为其引用,调用str1.intern()时会把它的引用放到常量池中并返回,因此是同一个引用。

(2)在(1)中已经放在常量池了,因此str2.intern()返回的是str1,与str2不相等。

例子4

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

结果:false

分析:按理说与上个例子的(1)同样,应该为true才对。问题在于java它是一个比较特殊的字符串,已经在常量池中存在了,因此不相等。至于为什么会存在,个人猜测是有两种可能:其它JDK的Java代码有该常量;JVM代码直接把某些特殊字符串放到了常量池。这个没有深究了。

3.2 JDK 6的不一样

当咱们知道了原理后,不一样表现就能够很容易判断出来了。以下例子:

String str1 = new StringBuilder("pk").append("slow").toString();
System.out.println(str1.intern() == str1);

JDK 6结果:false

JDK 8结果:true

由于JDK 6对于首次遇到的字符串,会复制一份到常量池并返回其引用,与str1的引用不是同一个,因此为false。而JDK 8只是将str1的引用在常量池记录而后返回,仍是同一个,因此为true。

知道了基本原理,更多状况就能够具体分析了,再也不一一赘述。

4 一种少见的应用

以前已经说过,String.intern()方法本质就是维持了一个String的常量池,并且池里的String应该都是惟一的。这样,咱们即可以利用这种惟一性,来作一些文章了。咱们能够利用池里String的对象来作锁,实现对资源的控制。好比一个城市的某种资源同一时间只能一个线程访问,那就能够把城市名的String对象做为锁,放到常量池中去,同一时间只能一个线程得到。

具体代码以下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class StringInternMultiThread {
    private String city;

    public StringInternMultiThread(String city) {
        this.city = city;
    }

    public void handle() {
        synchronized (this.city.intern()) {
            System.out.println(city + ":Fetched the lock");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(city + ":Release the lock");
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(6);
        StringInternMultiThread guangzhou = new StringInternMultiThread("Guangzhou");
        StringInternMultiThread shenzhen = new StringInternMultiThread("Shenzhen");
        StringInternMultiThread beijing = new StringInternMultiThread("Beijing");
        executorService.execute(guangzhou::handle);
        executorService.execute(guangzhou::handle);
        executorService.execute(guangzhou::handle);
        executorService.execute(shenzhen::handle);
        executorService.execute(shenzhen::handle);
        executorService.execute(beijing::handle);

        executorService.shutdown();
    }
}

运行结果以下:

Guangzhou:Fetched the lock
Shenzhen:Fetched the lock
Beijing:Fetched the lock
Beijing:Release the lock
Shenzhen:Release the lock
Guangzhou:Release the lock
Shenzhen:Fetched the lock
Guangzhou:Fetched the lock
Shenzhen:Release the lock
Guangzhou:Release the lock
Guangzhou:Fetched the lock
Guangzhou:Release the lock

能够看出,同一时间同一个城市不会同时得到资源,而不一样城市能够同时得到资源来处理。这种案例其实有其它更优雅的方案,这不是本文的重点,就不赘述了。

5 总结

本文介绍了String.intern()方法的原理和不一样JDK版本的表现,并经过多个例子与一个应用加深理解。但愿对你们理解String和JVM有帮助。


欢迎关注公众号<南瓜慢说>,将持续为你更新...

file

多读书,多分享;多写做,多整理。

相关文章
相关标签/搜索