constant-pool.md 9.6 KB
Newer Older
沉默王二's avatar
沉默王二 已提交
1
---
沉默王二's avatar
沉默王二 已提交
2 3
title: 深入理解Java字符串String常量池
shortTitle: 深入理解Java字符串常量池
沉默王二's avatar
沉默王二 已提交
4 5 6
category:
  - Java核心
tag:
沉默王二's avatar
沉默王二 已提交
7
  - 数组&字符串
沉默王二's avatar
沉默王二 已提交
8
description: 本文详细介绍了Java字符串常量池的概念和工作原理。了解字符串常量池如何提高程序性能和内存利用率,探讨其在Java开发中的实际应用。学习Java字符串常量池的特性,更深入地理解Java内存管理和优化策略。
沉默王二's avatar
沉默王二 已提交
9 10 11
head:
  - - meta
    - name: keywords
沉默王二's avatar
沉默王二 已提交
12
      content: Java,java字符串,java String,java常量池,java字符串常量池,string常量池,字符串常量池
沉默王二's avatar
沉默王二 已提交
13
---
沉默王二's avatar
沉默王二 已提交
14

沉默王二's avatar
沉默王二 已提交
15
# 4.6 深入理解Java字符串常量池
沉默王二's avatar
沉默王二 已提交
16

沉默王二's avatar
沉默王二 已提交
17 18 19 20 21
“三妹,今天我们来学习一下字符串常量池,这是字符串中非常关键的一个知识点。”我话音未落,青岛路小学那边传来了嘹亮的歌声就钻进了我的耳朵,“唱 ~ 山 ~ 歌 ~”,我都有点情不自禁地哼唱起来了。

三妹赶紧拦住我说,“好了,开始吧,哥。”

### new String("二哥")创建了几个对象
沉默王二's avatar
沉默王二 已提交
22 23 24 25 26 27 28

“先从这道面试题开始吧!”

```java
String s = new String("二哥");
```

沉默王二's avatar
沉默王二 已提交
29
“这行代码创建了几个[对象](https://tobebetterjavaer.com/oo/object-class.html)?”
沉默王二's avatar
沉默王二 已提交
30 31 32 33 34 35 36

“不就一个吗?”三妹不假思索地回答。

“不,两个!”我直接否定了三妹的答案,“使用 new 关键字创建一个字符串对象时,Java 虚拟机会先在字符串常量池中查找有没有‘二哥’这个字符串对象,如果有,就不会在字符串常量池中创建‘二哥’这个对象了,直接在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的对象地址返回赋值给变量 s。”

“如果没有,先在字符串常量池中创建一个‘二哥’的字符串对象,然后再在堆中创建一个‘二哥’的字符串对象,然后将堆中这个‘二哥’的字符串对象地址返回赋值给变量 s。”

沉默王二's avatar
沉默王二 已提交
37 38 39 40 41 42 43
我画图表示一下,会更加清楚。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-6dee151e-3a13-4f85-b870-3c9d1797557a.png)

在Java中,栈上存储的是基本数据类型的变量和对象的引用,而对象本身则存储在堆上。

对于这行代码 `String s = new String("二哥");`,它创建了两个对象:一个是字符串对象 "二哥",它被添加到了字符串常量池中,另一个是通过 new String() 构造函数创建的字符串对象 "二哥",它被分配在堆内存中,同时引用变量 s 存储在栈上,它指向堆内存中的字符串对象 "二哥"。
沉默王二's avatar
沉默王二 已提交
44

沉默王二's avatar
沉默王二 已提交
45
**为什么要先在字符串常量池中创建对象,然后再在堆上创建呢**?这样不就多此一举了?”三妹敏锐地发现了问题。
沉默王二's avatar
沉默王二 已提交
46

沉默王二's avatar
沉默王二 已提交
47 48 49 50 51
我回答,“是的。由于字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一块空间——也就是字符串常量池。”

### 字符串常量池的作用

通常情况下,我们会采用双引号的方式来创建字符串对象,而不是通过 new 关键字的方式,就像下面👇🏻这样,这样就不会多此一举:
沉默王二's avatar
沉默王二 已提交
52 53 54 55 56 57 58

```java
String s = "三妹";
```

当执行 `String s = "三妹"` 时,Java 虚拟机会先在字符串常量池中查找有没有“三妹”这个字符串对象,如果有,则不创建任何对象,直接将字符串常量池中这个“三妹”的对象地址返回,赋给变量 s;如果没有,在字符串常量池中创建“三妹”这个对象,然后将其地址返回,赋给变量 s。

沉默王二's avatar
沉默王二 已提交
59 60 61 62 63 64

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-80ca8b18-2446-431e-98e3-b194e1c608e3.png)

Java 虚拟机创建了一个字符串对象 "三妹",它被添加到了字符串常量池中,同时引用变量 s 存储在栈上,它指向字符串常量池中的字符串对象 "三妹"。你看,是不是省了一步,比之前高效了。


沉默王二's avatar
沉默王二 已提交
65 66 67 68 69 70 71 72 73 74 75
“哦,我明白了,哥。”三妹突然插话到,“有了字符串常量池,就可以通过双引号的方式直接创建字符串对象,不用再通过 new 的方式在堆中创建对象了,对吧?”

“是滴。new 的方式始终会创建一个对象,不管字符串的内容是否已经存在,而双引号的方式会重复利用字符串常量池中已经存在的对象。”我说。

来看下面这个例子:

```java
String s = new String("二哥");
String s1 = new String("二哥");
```

沉默王二's avatar
沉默王二 已提交
76
按照我们之前的分析,这两行代码会创建三个对象,字符串常量池中一个,堆上两个。
沉默王二's avatar
沉默王二 已提交
77 78 79 80 81 82 83 84 85 86

再来看下面这个例子:

```java
String s = "三妹";
String s1 = "三妹";
```

这两行代码只会创建一个对象,就是字符串常量池中的那个。这样的话,性能肯定就提高了!

沉默王二's avatar
沉默王二 已提交
87 88
### 字符串常量池在内存中的什么位置呢?

沉默王二's avatar
沉默王二 已提交
89 90 91 92
“那哥,字符串常量池在内存中的什么位置呢?”三妹问。

我说,“三妹,你这个问题问得好呀!”

沉默王二's avatar
沉默王二 已提交
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
分为三个阶段。

#### Java 7 之前

在 Java 7 之前,字符串常量池位于永久代(Permanent Generation)的内存区域中,主要用来存储一些字符串常量(静态数据的一种)。永久代是 Java 堆(Java Heap)的一部分,用于存储类信息、方法信息、常量池信息等静态数据。

而 Java 堆是 JVM 中存储对象实例和数组的内存区域,也就是说,永久代是 Java 堆的一个子区域。

换句话说,永久代中存储的静态数据与堆中存储的对象实例和数组是分开的,它们有不同的生命周期和分配方式。

但是,永久代和堆的大小是相互影响的,因为它们都使用了 JVM 堆内存,因此它们的大小都受到 JVM 堆大小的限制。

于是,当我们创建一个字符串常量时,它会被储存在永久代的字符串常量池中。如果我们创建一个普通字符串对象,则它将被储存在堆中。如果字符串对象的内容是一个已经存在于字符串常量池中的字符串常量,那么这个对象会指向已经存在的字符串常量,而不是重新创建一个新的字符串对象。

画幅图,大概就是这个样子。


![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-ed6518ec-1d51-4718-ab8a-e1e2cda774bd.png)


#### Java 7

需要注意的是,永久代的大小是有限的,并且很难准确地确定一个应用程序需要多少永久代空间。如果我们在应用程序中使用了大量的类、方法、常量等静态数据,就有可能导致永久代空间不足。这种情况下,JVM 就会抛出 OutOfMemoryError 错误。

因此,从 Java 7 开始,为了解决永久代空间不足的问题,将字符串常量池从永久代中移动到堆中。这个改变也是为了更好地支持动态语言的运行时特性。

再画幅图,大概就是这样子。

![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-f5231378-a442-421e-a470-8256da1715e8.png)

#### Java 8

到了 Java 8,永久代(PermGen)被取消,并由元空间(Metaspace)取代。元空间是一块本机内存区域,和 JVM 内存区域是分开的。不过,元空间的作用依然和之前的永久代一样,用于存储类信息、方法信息、常量池信息等静态数据。

与永久代不同,元空间具有一些优点,例如:

- 它不会导致 OutOfMemoryError 错误,因为元空间的大小可以动态调整。
- 元空间使用本机内存,而不是 JVM 堆内存,这可以避免堆内存的碎片化问题。
- 元空间中的垃圾收集与堆中的垃圾收集是分离的,这可以避免应用程序在运行过程中因为进行类加载和卸载而频繁地触发 Full GC。

再画幅图,对比来看一下,就会一目了然。

沉默王二's avatar
沉默王二 已提交
135

沉默王二's avatar
沉默王二 已提交
136
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-422e3214-97df-41ec-bcb5-132cfc76b669.png)
沉默王二's avatar
沉默王二 已提交
137

沉默王二's avatar
沉默王二 已提交
138
### 永久代、方法区、元空间
沉默王二's avatar
沉默王二 已提交
139 140 141 142


“哥,能再简单给我解释一下方法区,永久代和元空间的概念吗?有点模糊。”三妹说。

沉默王二's avatar
沉默王二 已提交
143
“可以呀。”
沉默王二's avatar
沉默王二 已提交
144

沉默王二's avatar
沉默王二 已提交
145
- 方法区是 Java 虚拟机规范中的一个概念,就像是一个[接口](https://tobebetterjavaer.com/oo/interface.html)吧;
沉默王二's avatar
沉默王二 已提交
146 147
- 永久代是 HotSpot 虚拟机中对方法区的一个实现,就像是接口的实现类;
- Java 8 的时候,移除了永久代,取而代之的是元空间,是方法区的另外一种实现,更灵活了。
沉默王二's avatar
沉默王二 已提交
148

沉默王二's avatar
沉默王二 已提交
149
永久代是放在运行时数据区中的,所以它的大小受到 Java 虚拟机本身大小的限制,所以 Java 8 之前,会经常遇到 `java.lang.OutOfMemoryError: PremGen Space` 的异常,PremGen Space 就是方法区的意思;而元空间是直接放在内存中的,所以只受本机可用内存的限制。
沉默王二's avatar
沉默王二 已提交
150 151 152 153 154 155 156 157 158

“明白了吧,三妹?”我问。

“嗯嗯。”三妹回答。

“那关于字符串常量池,就先说这么多吧,是不是还挺有意思的。”我说。

“是的,我现在是彻底搞懂了字符串常量池,哥,你真棒!”三妹说。

沉默王二's avatar
沉默王二 已提交
159 160 161

---

沉默王二's avatar
沉默王二 已提交
162
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
沉默王二's avatar
沉默王二 已提交
163

沉默王二's avatar
沉默王二 已提交
164
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
沉默王二's avatar
沉默王二 已提交
165

沉默王二's avatar
沉默王二 已提交
166
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)