前言

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. 推荐给 Java 程序员的 7 本书

    < Java 编程思想> 适合各个阶段 Java 程序员的必备读物.书中对 Java 进行了详尽的介绍,与其它语言做了对比,解释了 Java 很多特性出现的原因和解决的问题.初学者可以通过 ...

  2. OpenCascade拓扑对象之:TopoDS_Shape对象及其子对象

    @font-face { font-family: "Times New Roman" } @font-face { font-family: "宋体" } @ ...

  3. 阿里云app原型设计

    软件需求分析与系统设计 https://edu.cnblogs.com/campus/zswxy/2018SE 这个作业要求在哪里 https://edu.cnblogs.com/campus/zsw ...

  4. java的高并发IO原理,阻塞BIO同步非阻塞NIO,异步非阻塞AIO

    原文地址: IO读写的基础原理 大家知道,用户程序进行IO的读写,依赖于底层的IO读写,基本上会用到底层的read&write两大系统调用.在不同的操作系统中,IO读写的系统调用的名称可能不完 ...

  5. FullCalendar v5.3.2版本制作一个航班日历Demo

    今天一个新需求是制作一个航班日历来订舱.然后我就各种找,最后找到FullCalendar,过程非常煎熬,网上例子大部分没用,大部分没有完整版.官网教程又不是很详细.搞了几天才彻底搞好这个航班日历,有需 ...

  6. deepin 20 镜像源

    deepin 20 镜像源 ## Generated by deepin-installerdeb [by-hash=force] https://community-packages.deepin. ...

  7. python_面向对象_组合

    组合: 一个类的对象是另外一个类对象的属性 # 组合 # 一个类的对象是另一个类对象的属性 # 什么时候使用组合:当两个类之间的关系是 :什么有什么的关系 : 班级有学生 学生有班级 班级有课程 图书 ...

  8. Martini初步

    部分内容来自http://jerkwin.github.io/9999/08/01/Martini%E7%B2%97%E7%B2%92%E5%8C%96%E5%8A%9B%E5%9C%BA%E4%BD ...

  9. pandas.DataFarme内置的绘图功能参数说明

    可视化是数据探索性分析及结果表达的一种非常重要的形式,因此打算写一个python绘图系列,本文是第一篇,先说一下pandas.DataFrame.plot()绘图功能. pandas.DataFram ...

  10. UNP——第二章,TCP握手与挥手分析

    1.握手 说明: 下面涉及 FIN,SYN,ACK之类数据时,都是由TCP服务收发, 涉及 accept, listen 之类api,都是 应用进程 完成. 都统一使用 客户端,服务端描述,请自行分辨 ...