悠哉杂学 - String
type: channel
link: https://uinit.cn/index.php/tag/String/
items: 详解String-字符串常量池与String.intern()

详解String-字符串常量池与String.intern()
type: item
date: 2021-08-19T13:17:00+08:00
description: 0. Background在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快，更节省内存，都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。8种基本类型的常量池都是系统协调的，String类型的常量池比较特殊。它的主要使用方法有两种：直接使用双引号声明出来的String对象会直接存储在常量池中。如果不是用双引号声明的String对象，可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在，若不存在就会将当前字符串放入常量池中1.常量池1.1 常量池是什么？JVM常量池主要分为Class文件常量池、运行时常量池，全局字符串常量池，以及基本类型包装类对象常量池1.1.0 方法区方法区的作用是存储Java类的结构信息，当创建对象后，对象的类型信息存储在方法区中，实例数据存放在堆中。类型信息是定义在Java代码中的常量、静态变量、以及类中声明的各种方法，方法字段等；实例数据则是在Java中创建的对象实例以及他们的值。该区域进行内存回收的主要目的是对常量池的回收和对内存数据的卸载；一般说这个区域的内存回收率比起Java堆低得多。1.1.1 Class文件常量池class文件是一组以字节为单位的二进制数据流，在Java代码的编译期间，我们编写的Java文件就被编译为.class文件格式的二进制数据存放在磁盘中，其中就包括class文件常量池。class文件常量池主要存放两大常量：字面量和符号引用。字面量：字面量接近java语言层面的常量概念文本字符串，也就是我们经常申明的：public String s = "abc";中的"abc"用final修饰的成员变量，包括静态变量、实例变量和局部变量：public final static int f = 0x101;,final int temp = 3;而对于基本类型数据(甚至是方法中的局部变量)，如int value = 1常量池中只保留了他的的字段描述符int和字段的名称value，他们的字面量不会存在于常量池。符号引用：符号引用主要设涉及编译原理方面的概念类和接口的全限定名，也就是java/lang/String;这样，将类名中原来的".“替换为”/"得到的，主要用于在运行时解析得到类的直接引用字段的名称和描述符，字段也就是类或者接口中声明的变量，包括类级别变量和实例级的变量方法中的名称和描述符，也即参数类型+返回值1.1.2 运行时常量池当Java文件被编译成class文件之后，会生成上面的class文件常量池，JVM在执行某个类的时候，必须经过加载、链接（验证、准备、解析）、初始化的步鄹，运行时常量池则是在JVM将类加载到内存后，就会将class常量池中的内容存放到运行时常量池中，也就是class常量池被加载到内存之后的版本，是方法区的一部分。在解析阶段，会把符号引用替换为直接引用，解析的过程会去查询字符串常量池，也就StringTable，以保证运行时常量池所引用的字符串与字符串常量池中是一致的。运行时常量池相对于class常量池一大特征就是具有动态性，Java规范并不要求常量只能在运行时才产生，也就是说运行时常量池的内容并不全部来自class常量池，在运行时可以通过代码生成常量并将其放入运行时常量池中，这种特性被用的最多的就是String.intern()。1.1.3 字符串常量池在JDK6.0及之前版本，字符串常量池存放在方法区中，在JDK7.0版本以后，字符串常量池被移到了堆中了。至于为什么移到堆内，大概是由于方法区的内存空间太小了。在HotSpot VM里实现的string pool功能的是一个StringTable类，它是一个Hash表，默认值大小长度是1009；这个StringTable在每个HotSpot VM的实例只有一份，被所有的类共享。字符串常量由一个一个字符组成，放在了StringTable上。在JDK6.0中，StringTable的长度是固定的，长度就是1009，因此如果放入String Pool中的String非常多，就会造成hash冲突，导致链表过长，当调用String#intern()时会需要到链表上一个一个找，从而导致性能大幅度下降；在JDK7.0中，StringTable的长度可以通过参数指定。字符串常量池设计思想：字符串的分配，和其他的对象分配一样，耗费高昂的时间与空间代价，作为最基础的数据类型，大量频繁的创建字符串，极大程度地影响程序的性能JVM为了提高性能和减少内存开销，在实例化字符串常量的时候进行了一些优化为字符串开辟一个字符串常量池，类似于缓存区创建字符串常量时，首先查看字符串常量池是否存在该字符串存在该字符串，…