Java String 演进全解析
前言
String 是我们使用最频繁的对象,使用不当会对内存、程序的性能造成影响,本篇文章全面介绍一下 Java 的 String 是如何演进的,以及使用 String 的注意事项。
下面的输出结果是什么?
@Test
public void testString() {
String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
System.out.println(str1 == str2);
System.out.println(str2 == str3);
System.out.println(str1 == str3);
}
这段代码涉及了 Java 字符串的内存分配、新建对象和引用等方面的知识,输出结果是:
false
false
true
String 对象的实现方式
String 对象的实现方式,在 Java 6、Java 7/8、Java 9 中都有很大的区别。下面是一张简要的对比图:

Java 6 的实现方式
String 对 char 数组进行了封装,主要有四个成员变量:
- char 数组
- 偏移量 offset
- 字符数量 count
- 哈希值 hash
String 对象可以通过 offset 和 count 在 char[] 数组中获取对应的字符串,这样做可以高效、快速地共享数组对象,节省内存空间,但是这种方法经常导致内存泄漏。
这是因为,假如有一个非常大的字符串数组对象 a,后来有一个小的字符串引用仅引用其中很少的字符 b,那么会新建大的数组 char[],当 a 被释放后,char[] 的引用并不能被 GC,因为 b 还在引用。
Java 7/8 的实现方式
String 类去掉了 offset 和 count,String.substring 方法也不再共享char[],从而解决了内存泄漏问题。
Java 9 的实现方式
char[] → byte[],同时新增了coder属性,标识字符编码。这是因为 char 字符占 16 位(2个字节),如果仅存储单字节编码的字符就非常浪费空间。
coder 属性的作用是标识字符串是否为 Latin-1(单字节编码),0 标识是 Latin-1,1 代表是 UTF-16。
Java 11 中的 java.lang.String#substring(int, int) 方法如下:
public String substring(int beginIndex, int endIndex) {
int length = length();
checkBoundsBeginEnd(beginIndex, endIndex, length);
int subLen = endIndex - beginIndex;
if (beginIndex == 0 && endIndex == length) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
String 在 JVM 中是如何存储的?
这是一个很重要的问题,相信大部分人都不能描述清楚,因为 JVM 的实现改了很多版……
在 JDK 1.7 之前,运行时常量池逻辑包含字符串常量池,都存在方法区中,方法区在 HotSpot 虚拟机的实现为永久代。
在 JDK 1.7 中,字符串常量池 → 堆,运行时常量池仍然在方法区中。
在 JDK 1.8 中,HotSpot 移除了永久代,使用元空间(Metaspace)代替。这时候字符串常量池在堆中,运行时常量池在元空间(Metaspace)。
永久代 VS 元空间(Metaspace)
元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。
一句话总结
在新版 JDK 实现(毕竟 Java 8 都已经是老古董,Java 15 都发布了)中,字符串常量池是在堆中。
使用 String.intern 节省内存
虽然我还没有在项目中实际应用过,不过这个函数应该还挺有用的,能够复用 Java 中的字符串常量。文章开头的代码中,System.out.println(str1 == str3); 返回 true,就是因为 java.lang.String#intern 方法检测到字符串常量池有这个对象时,能够直接复用字符串常量池的对象,不会额外创建字符串常量。
String str1 = "abc";
String str2 = new String("abc");
注意上面的代码中,new String("abc") 里面的字符串 abc 与 str1 的 abc 不同,是在字符串常量池新创建的 abc。
String.intern 的代码注释如下。
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
* @jls 3.10.5 String Literals
*/
public native String intern();
公众号
coding 笔记、点滴记录,以后的文章也会同步到公众号(Coding Insight)中,希望大家关注_
代码和思维导图在 GitHub 项目中,欢迎大家 star!

Java String 演进全解析的更多相关文章
- 转载文章 208 个最常见 Java 面试题全解析
最近正值春招,一直在给公司招聘 Java 程序员,我从 2015 年做 TeamLeader 开始就习惯性地收集平时遇到的 Java 技术问题或周围朋友见过的面试题,经过不断筛选,终于凝练成一套实用的 ...
- Java String源码解析
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ...
- Java集合最全解析,学集合,看这篇就够用了!!!
在看集合类之前, 我们要先明白一下概念: 1.数据结构 (1):线性表 [1]:顺序存储结构(也叫顺序表) 一个线性表是n个具有相同特性的数据元素的有限序列.数据元素是一个抽象的符号,其具体含义在不同 ...
- 《Java面试全解析》505道面试题详解
<Java面试全解析>是我在 GitChat 发布的一门电子书,全书总共有 15 万字和 505 道 Java 面试题解析,目前来说应该是最实用和最全的 Java 面试题解析了. 我本人是 ...
- 《Java面试全解析》1000道面试题大全详解(转)
<Java面试全解析>1000道 面试题大全详解 本人是 2009 年参加编程工作的,一路上在技术公司摸爬滚打,前几年一直在上海,待过的公司有 360 和游久游戏,因为自己家庭的原因,放弃 ...
- Java JVM 内存泄漏--全解析和处理办法 [ 转载 ]
Java JVM 内存泄露——全解析和处理办法 [转载] @author 小筐子 @address http://www.jianshu.com/p/bf159a9c391a JA ...
- Java IO编程全解(一)——Java的I/O演进之路
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7419117.html JDK1.4之前的早期版本,Java对I/O的支持并不完善,开发人员在开发高性能I/O ...
- Java生鲜电商平台-电商订单系统全解析
Java生鲜电商平台-电商订单系统全解析 说明:Java生鲜电商平台-电商订单系统全解析主要讲解OMS的内容,设计,开发,架构等知识. 今天分享将会分为以下三个环节来阐述: 1.订单系统的介绍 2.订 ...
- Java并发指南13:Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 转自https://www.javadoop.com/post/hashmap#toc7 部分内容转自 http: ...
随机推荐
- git同步源码到gitee和github
如何把我们的源码同步到gitee或github远程仓库中 同步方式分以下几种: 1.命令同步 先查看下我们是否有远程仓库:git remote -v 如有就要删除远程仓库或是同命令覆盖,如全新安 ...
- AE(After Effects)的简单使用——记一次模板套用的过程
一.环境配置 操作系统:win10 64位 软件版本:AE16.11版 二.模板下载 可去 newcger 网站进行模板下载,常用都是免费的,很好用,很推荐 三.具体操作 1.模板素材替换 点击[文件 ...
- MySQL 使用规范总结
MySQL已经成为世界上最受欢迎的数据库管理系统之一,无论是用在小型开发项目上,还是用在构建那较大型的网站,MySQL都用实力证明了自己是一个稳定.可靠.快速.可信的系统,足以胜任任何数据存储业务的需 ...
- Docker系列03—Docker 存储卷
一.存储卷介绍 1.1 背景 Docker 的 AFUS 分层文件系统 docker镜像由多个只读层叠加而成,启动容器时,docker会加载只读镜像层并在镜像栈顶部加一个读写层: 如果运行的容器修改了 ...
- DB2添加联合主键
CREATE TABLE EQUIPMENT_DAILY_CAL( EQU_DATE TIME NOT NULL ,/*日期*/ SEQ_ID INTEGER NOT NULL,/*序号 */ FAU ...
- DevExpress XtraReport报表预览时可编辑的功能
设置控件的EditOptions.Enabled=true即可 注册实现PrintingSystem. EditingFieldChanged事件,可获得当前发生更改的控件的值. 另:XtraRepo ...
- 对于STM32F103的USART的通讯调试
USART:(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步串行接收/发送器USART是一个全双工通用同步/异步串行收发 ...
- margin的讲究
什么元素允许有margin值,无论块状元素还是行内元素都可以,只是各有限制. 先说行内元素,这个是不允许有上下 外边距的, 再说块状元素,上下左右外边距都允许 但是相邻元素的外边距会合并,要注意的是 ...
- JS缓冲运动案例
点击"向右"按钮,红色的#red区块开始向右缓冲运动,抵达到黑色竖线位置自动停止,再次点击"向右"#red区块也不会再运动.点击"向左"按钮 ...
- 重看 mb volatile atomic
在单处理器情况下,每条指令的执行都是原子性的,但在多处理器情况下,只有那些单独的读操作或写操作才是原子性的.为了弥补这一缺点,x86提供了附加的lock前缀,使带lock前缀的读修改写指令也能原子性执 ...