前言

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&trade; 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 演进全解析的更多相关文章

  1. 转载文章 208 个最常见 Java 面试题全解析

    最近正值春招,一直在给公司招聘 Java 程序员,我从 2015 年做 TeamLeader 开始就习惯性地收集平时遇到的 Java 技术问题或周围朋友见过的面试题,经过不断筛选,终于凝练成一套实用的 ...

  2. Java String源码解析

    public final class String implements java.io.Serializable, Comparable<String>, CharSequence { ...

  3. Java集合最全解析,学集合,看这篇就够用了!!!

    在看集合类之前, 我们要先明白一下概念: 1.数据结构 (1):线性表 [1]:顺序存储结构(也叫顺序表) 一个线性表是n个具有相同特性的数据元素的有限序列.数据元素是一个抽象的符号,其具体含义在不同 ...

  4. 《Java面试全解析》505道面试题详解

    <Java面试全解析>是我在 GitChat 发布的一门电子书,全书总共有 15 万字和 505 道 Java 面试题解析,目前来说应该是最实用和最全的 Java 面试题解析了. 我本人是 ...

  5. 《Java面试全解析》1000道面试题大全详解(转)

    <Java面试全解析>1000道 面试题大全详解 本人是 2009 年参加编程工作的,一路上在技术公司摸爬滚打,前几年一直在上海,待过的公司有 360 和游久游戏,因为自己家庭的原因,放弃 ...

  6. Java JVM 内存泄漏--全解析和处理办法 [ 转载 ]

    Java JVM 内存泄露——全解析和处理办法 [转载]   @author 小筐子 @address http://www.jianshu.com/p/bf159a9c391a         JA ...

  7. Java IO编程全解(一)——Java的I/O演进之路

    转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7419117.html JDK1.4之前的早期版本,Java对I/O的支持并不完善,开发人员在开发高性能I/O ...

  8. Java生鲜电商平台-电商订单系统全解析

    Java生鲜电商平台-电商订单系统全解析 说明:Java生鲜电商平台-电商订单系统全解析主要讲解OMS的内容,设计,开发,架构等知识. 今天分享将会分为以下三个环节来阐述: 1.订单系统的介绍 2.订 ...

  9. Java并发指南13:Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

    Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 转自https://www.javadoop.com/post/hashmap#toc7 部分内容转自 http: ...

随机推荐

  1. 推荐系统---深度兴趣网络DIN&DIEN

    深度学习在推荐系统.CTR预估领域已经有了广泛应用,如wide&deep.deepFM模型等,今天介绍一下由阿里算法团队提出的深度兴趣网络DIN和DIEN两种模型 paper DIN:http ...

  2. 机器学习之K均值算法(K-means)聚类

    K均值算法(K-means)聚类 [关键词]K个种子,均值 一.K-means算法原理 聚类的概念:一种无监督的学习,事先不知道类别,自动将相似的对象归到同一个簇中. K-Means算法是一种聚类分析 ...

  3. mysql增删改查语法

    MySQL 关注公众号"轻松学编程"了解更多. MySQL是关系型数据库,对大小写不敏感. MySQL属于关系型数据库, 优点: 复杂查询 可以用SQL语句方便的在一个表以及多个表 ...

  4. codeforces 1442 A. Extreme Subtraction(贪心,构造)

    传送门 样例(x): 8 15 16 17 19 27 36 29 33 结果(t1) 15 15 16 18 26 35 28 32 思路:我们可以把最左端和最右端当做两个水龙头,每个水龙头流量的上 ...

  5. SVG--D3--血缘关系树

    最近的工作与可视化有关,有展示血缘关系树的需求 ,类似于这样: 碰巧搜到 D3(用于可视化的js库,作者吕之华),瞬间无法自拔,它的树状图功能基于SVG.js ,暴露的可操作入口也简洁恰当,能帮助你快 ...

  6. 微信小程序获取普通二维码

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. Core WebApi项目快速入门(一):环境部署

    1.WebApi新建与部署 1.1 新建Core WebApi工程 1.2 部署 1.2.1 IIS部署 首先以文件方式发布应用程序,然后下载依赖.net core运行时及host安装包 在iis中看 ...

  8. uni-app 动态控制下拉刷新

    扫码查看原文,搜索uni-app 动态控制下拉刷新: 前置条件: 开发环境:windows 开发框架:uni-app , H5+,nativeJS 编辑器:HbuilderX 2.8.13 4.兼容版 ...

  9. Docker(32)- 如何修改 docker 容器的启动参数

    如果你还想从头学起 Docker,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1870863.html 前言 有时候创建容器时忘了添加  ...

  10. xenserver使用ceph的rbd的方法

    首先安装的xenserver6.5的环境,看到有地方有提到这个上面可以安装rbd的支持,网上有一种方式是libvirt+kvm方式,因为ceph对libviet是原生支持的,但是xenserver底层 ...