【Java必修课】String.intern()原来还能这么用(原理与应用)
1 简介
String.intern()是JDK一早就提供的native方法,不由Java实现,而是底层JVM实现,这让我们对它的窥探提高了难度。特别是在Oracle收购了Sun公司后,源代码不开源了,更无法深入研究了。但我们还是有必要尽量地去探索。
本文将主要讲解一下String.intern()方法的原理、特点,并介绍一个新奇的应用。
2 String的池化
方法intern()的作用就是将String池化,这个池是String的常量池。不同版本的JDK有不同的实现。
2.1 不同实现与不同内存空间
- JDK 6:
intern()方法会把首先遇到的字符串复制一份到永久代中,然后返回永久中的实例引用;如果不是首次,说明常量池中已经有该字符串,直接返回池中的引用。常量池在永久代(PermGen)中。 - JDK 7:
intern()方法首次遇到字符串时,不会复制实例,而是直接把该字符串的引用记录在常量池中,并返回该引用;如果不是首次,则直接返回池中引用。JDK 7常量池在堆中。 - JDK 8:功能与JDK 7类似。常量池在元空间Metaspace中,元空间不在虚拟机内存中,而是使用本地内存。
2.2 常量池大小差异
这个所谓的String常量池,其实就是一张哈希表,跟HashMap类似,所以也是有大小限制和哈希冲突可能。常量池越大,哈希冲突可能性越小。
JDK 6早期版本,池大小为常量1009,后期变得可配置,通过参数
-XX:StringTableSize=N指定。大小也会受限于永久代的大小,建议避免使用intern()方法,防止造成PermGen内存溢出。JDK 7将常量池移到堆后,可以存放更多常量,也一样通过参数可配置大小。在Java 7u40后,常量池默认大小增加到了60013。
JDK 8默认大小一开始就是60013,依旧支持参数配置。
总的来说,-XX:StringTableSize的默认值在Java 7u40以前为1009,Java 7u40以后改为60013。
3 例子分析
通过例子,来理解一下就更清晰了。JDK 7和8应该表现一致,本文使用JDK 8。
3.1 JDK 8
先演示JDK 8的情况:
例子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有帮助。
欢迎关注公众号<南瓜慢说>,将持续为你更新...

多读书,多分享;多写作,多整理。
【Java必修课】String.intern()原来还能这么用(原理与应用)的更多相关文章
- java 字符串String.intern()方法学习
在jdk1.6与jdk1.7中,String类中的intern()方法实现的原理是有一些差异的.1.在jdk1.6中,intern()方法是先查找字符串常量池是否含有当前字符串,如果没有,那么就在字符 ...
- 深入理解Java String#intern() 内存模型
原文出处: codelog.me 大家知道,Java中string.intern()方法调用会先去字符串常量池中查找相应的字符串,如果字符串不存在,就会在字符串常量池中创建该字符串然后再返回. 字符串 ...
- Java中String的intern方法,javap&cfr.jar反编译,javap反编译后二进制指令代码详解,Java8常量池的位置
一个例子 public class TestString{ public static void main(String[] args){ String a = "a"; Stri ...
- String学习之-深入解析String#intern
引言 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念.常量池就类似一个JAVA系统级别提供的缓存. 8 ...
- 深入解析String#intern
转自:https://tech.meituan.com/in_depth_understanding_string_intern.html 深入解析String#intern john_yang ·2 ...
- (转载)深入解析String#intern
本文转载自:深入解析String#intern 引言 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念. ...
- 聊聊Java String.intern 背后你不知道的知识
Java的 String类有个有意思的public方法: public String intern() 返回标准表示的字符串对象.String类维护私有字符串池. 调用此方法时,如果字符串池已经包含等 ...
- Java提高篇——理解String 及 String.intern() 在实际中的应用
1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. ...
- java 创建string对象机制 字符串缓冲池 字符串拼接机制 字符串中intern()方法
字符串常量池:字符串常量池在方法区中 为了优化空间,为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池.如果字符串已经存在池中,就 ...
随机推荐
- HTML基础知识(块级标签,行内标签,行内块标签)
块级元素:独占一行,对宽高的属性值生效:如果不给宽度,块级元素就默认为浏览器的宽度,即就是100%宽: 行内元素:可以多个标签存在一行,对宽高属性值不生效,完全靠内容撑开宽高! 其中还有一种结合两种模 ...
- 数据的异构实战(一) 基于canal进行日志的订阅和转换
什么是数据的异构处理.简单说就是为了满足我们业务的扩展性,将数据从某种特定的格式转换到新的数据格式中来. 为什么会有这种需求出现呢? 传统的企业中,主要都是将数据存储在了关系型数据库中,例如说MySQ ...
- Kafka常用运维操作
创建主题kafka-topics.sh --zookeeper localhost:2181 --create --topic my-topic --replication-factor 3 --pa ...
- Java BIO及实现
发现日常学习过的知识不久就会遗忘,在此只是整理并记录一下学习笔记,做个回忆,并方便以后查阅,若有错误,欢迎指正 网络模型:TCP/IP网络模型是从OSI七层模型中演化来的,osi模型分为物理层,数据链 ...
- IT爱心求助站
最近发生的一些事情,让我对自己的专业有了另外一层认识. 小尹同学,你是做软件的是吗?能否帮我看一下我的电脑问题? 老同学,我的电脑安装一个软件这么都装不上,能否帮我看一下呢? 邻居你好,我的手机怎么没 ...
- 云计算之走进LINUX(二)
引言 * 第二部分 云计算应用管理 [Shell脚本基础] [使用变量] [条件测试及选择] [列表式循环] [系统安全保护] [配置用户环境] [防火墙策略管理] [ISCSI共享存储] [数据库 ...
- 像智能手机一样管理云端应用:阿里云联合微软全球首发开放应用模型(OAM)
2019 年 10 月 17 日上午 9 点 15 分,阿里巴巴合伙人.阿里云智能基础产品事业部总经理蒋江伟在 QCon 上海<基于云架构的研发模式演进>主题演讲中,正式宣布: " ...
- 《深入理解Java虚拟机》-----第13章 线程安全与锁优化
概述 在软件业发展的初期,程序编写都是以算法为核心的,程序员会把数据和过程分别作为独立的部分来考虑,数据代表问题空间中的客体,程序代码则用于处理这些数据,这种思维方式直接站在计算机的角度去抽象问题和解 ...
- spring在IoC容器中装配Bean详解
1.Spring配置概述 1.1.概述 Spring容器从xml配置.java注解.spring注解中读取bean配置信息,形成bean定义注册表: 根据bean定义注册表实例化bean: 将bean ...
- FormData交互以及Node multiparty插件的使用
一.FormData FormData是ajax2.0里面添加的新特性. FormData的主要用途有两个: (1).将form表单元素的name与value进行组合,实现表单数据的序列化,从而减少表 ...