Skip to content

Commit 985f7f2

Browse files
committedFeb 22, 2023
字符串常量池
1 parent 2626d5c commit 985f7f2

File tree

5 files changed

+384
-21
lines changed

5 files changed

+384
-21
lines changed
 

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
- [一文吃透Java数组](docs/array/array.md)
117117
- [聊聊Java的二维数组](docs/array/double-array.md)
118118
- [如何优雅地打印Java数组?](docs/array/print.md)
119-
- [为什么String是不可变的](docs/string/immutable.md)
119+
- [聊聊Java字符串,以及为什么String是不可变的](docs/string/immutable.md)
120120
- [深入理解Java字符串常量池](docs/string/constant-pool.md)
121121
- [深入解析String.intern()方法](docs/string/intern.md)
122122
- [Java如何判断两个字符串是否相等?](docs/string/equals.md)

‎docs/home.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ head:
125125
- [一文吃透Java数组](array/array.md)
126126
- [聊聊Java的二维数组](array/double-array.md)
127127
- [如何优雅地打印Java数组?](array/print.md)
128-
- [为什么String是不可变的](string/immutable.md)
128+
- [聊聊Java字符串,以及为什么String是不可变的](string/immutable.md)
129129
- [深入理解Java字符串常量池](string/constant-pool.md)
130130
- [深入解析String.intern()方法](string/intern.md)
131131
- [Java如何判断两个字符串是否相等?](string/equals.md)

‎docs/string/constant-pool.md

+78-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: 深入理解Java字符串常量池
3-
shortTitle: 深入理解Java字符串常量池
2+
title: 深入理解Java的字符串常量池
3+
shortTitle: Java字符串常量池
44
category:
55
- Java核心
66
tag:
@@ -9,12 +9,16 @@ description: Java程序员进阶之路,小白的零基础Java教程,从入
99
head:
1010
- - meta
1111
- name: keywords
12-
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java字符串,String,常量池
12+
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java字符串,java String,java常量池,字符串常量池,string
1313
---
1414

15-
“三妹,今天我们来学习一下字符串常量池吧,这是字符串中非常关键的一个知识点。”我话音未落,青岛路小学那边传来了嘹亮的歌声就钻进了我的耳朵,“唱 ~ 山 ~ 歌 ~
15+
## 深入理解Java的字符串常量池
1616

17-
三妹说,“好呀,开始吧,哥。”
17+
“三妹,今天我们来学习一下字符串常量池,这是字符串中非常关键的一个知识点。”我话音未落,青岛路小学那边传来了嘹亮的歌声就钻进了我的耳朵,“唱 ~ 山 ~ 歌 ~”,我都有点情不自禁地哼唱起来了。
18+
19+
三妹赶紧拦住我说,“好了,开始吧,哥。”
20+
21+
### new String("二哥")创建了几个对象
1822

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

@@ -30,18 +34,34 @@ String s = new String("二哥");
3034

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

33-
“为什么要先在字符串常量池中创建对象,然后再在堆上创建呢?这样不就多此一举了?”三妹敏锐地发现了问题。
37+
我画图表示一下,会更加清楚。
38+
39+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-6dee151e-3a13-4f85-b870-3c9d1797557a.png)
40+
41+
在Java中,栈上存储的是基本数据类型的变量和对象的引用,而对象本身则存储在堆上。
42+
43+
对于这行代码 `String s = new String("二哥");`,它创建了两个对象:一个是字符串对象 "二哥",它被添加到了字符串常量池中,另一个是通过 new String() 构造函数创建的字符串对象 "二哥",它被分配在堆内存中,同时引用变量 s 存储在栈上,它指向堆内存中的字符串对象 "二哥"。
3444

35-
我回答,“由于字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一个字符串常量池。”
45+
**为什么要先在字符串常量池中创建对象,然后再在堆上创建呢**?这样不就多此一举了?”三妹敏锐地发现了问题。
3646

37-
通常情况下,我们会采用双引号的方式来创建字符串对象,而不是通过 new 关键字的方式:
47+
我回答,“是的。由于字符串的使用频率实在是太高了,所以 Java 虚拟机为了提高性能和减少内存开销,在创建字符串对象的时候进行了一些优化,特意为字符串开辟了一块空间——也就是字符串常量池。”
48+
49+
### 字符串常量池的作用
50+
51+
通常情况下,我们会采用双引号的方式来创建字符串对象,而不是通过 new 关键字的方式,就像下面👇🏻这样,这样就不会多此一举:
3852

3953
```java
4054
String s = "三妹";
4155
```
4256

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

59+
60+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-80ca8b18-2446-431e-98e3-b194e1c608e3.png)
61+
62+
Java 虚拟机创建了一个字符串对象 "三妹",它被添加到了字符串常量池中,同时引用变量 s 存储在栈上,它指向字符串常量池中的字符串对象 "三妹"。你看,是不是省了一步,比之前高效了。
63+
64+
4565
“哦,我明白了,哥。”三妹突然插话到,“有了字符串常量池,就可以通过双引号的方式直接创建字符串对象,不用再通过 new 的方式在堆中创建对象了,对吧?”
4666

4767
“是滴。new 的方式始终会创建一个对象,不管字符串的内容是否已经存在,而双引号的方式会重复利用字符串常量池中已经存在的对象。”我说。
@@ -64,27 +84,69 @@ String s1 = "三妹";
6484

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

87+
### 字符串常量池在内存中的什么位置呢?
88+
6789
“那哥,字符串常量池在内存中的什么位置呢?”三妹问。
6890

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

71-
在 Java 8 之前,字符串常量池在永久代中。
93+
分为三个阶段。
94+
95+
#### Java 7 之前
96+
97+
在 Java 7 之前,字符串常量池位于永久代(Permanent Generation)的内存区域中,主要用来存储一些字符串常量(静态数据的一种)。永久代是 Java 堆(Java Heap)的一部分,用于存储类信息、方法信息、常量池信息等静态数据。
98+
99+
而 Java 堆是 JVM 中存储对象实例和数组的内存区域,也就是说,永久代是 Java 堆的一个子区域。
100+
101+
换句话说,永久代中存储的静态数据与堆中存储的对象实例和数组是分开的,它们有不同的生命周期和分配方式。
102+
103+
但是,永久代和堆的大小是相互影响的,因为它们都使用了 JVM 堆内存,因此它们的大小都受到 JVM 堆大小的限制。
104+
105+
于是,当我们创建一个字符串常量时,它会被储存在永久代的字符串常量池中。如果我们创建一个普通字符串对象,则它将被储存在堆中。如果字符串对象的内容是一个已经存在于字符串常量池中的字符串常量,那么这个对象会指向已经存在的字符串常量,而不是重新创建一个新的字符串对象。
106+
107+
画幅图,大概就是这个样子。
108+
109+
110+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-ed6518ec-1d51-4718-ab8a-e1e2cda774bd.png)
111+
112+
113+
#### Java 7
114+
115+
需要注意的是,永久代的大小是有限的,并且很难准确地确定一个应用程序需要多少永久代空间。如果我们在应用程序中使用了大量的类、方法、常量等静态数据,就有可能导致永久代空间不足。这种情况下,JVM 就会抛出 OutOfMemoryError 错误。
116+
117+
因此,从 Java 7 开始,为了解决永久代空间不足的问题,将字符串常量池从永久代中移动到堆中。这个改变也是为了更好地支持动态语言的运行时特性。
118+
119+
再画幅图,大概就是这样子。
120+
121+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-f5231378-a442-421e-a470-8256da1715e8.png)
122+
123+
#### Java 8
124+
125+
到了 Java 8,永久代(PermGen)被取消,并由元空间(Metaspace)取代。元空间是一块本机内存区域,和 JVM 内存区域是分开的。不过,元空间的作用依然和之前的永久代一样,用于存储类信息、方法信息、常量池信息等静态数据。
126+
127+
与永久代不同,元空间具有一些优点,例如:
128+
129+
- 它不会导致 OutOfMemoryError 错误,因为元空间的大小可以动态调整。
130+
- 元空间使用本机内存,而不是 JVM 堆内存,这可以避免堆内存的碎片化问题。
131+
- 元空间中的垃圾收集与堆中的垃圾收集是分离的,这可以避免应用程序在运行过程中因为进行类加载和卸载而频繁地触发 Full GC。
132+
133+
再画幅图,对比来看一下,就会一目了然。
134+
72135

73-
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string/constant-pool-01.png)
136+
![](http://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string//constant-pool-422e3214-97df-41ec-bcb5-132cfc76b669.png)
74137

75-
Java 8 之后,移除了永久代,字符串常量池就移到了堆中。
138+
### 永久代、方法区、元空间
76139

77-
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/string/constant-pool-02.png)
78140

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

81-
我说,“可以呀。”
143+
“可以呀。”
82144

83145
- 方法区是 Java 虚拟机规范中的一个概念,就像是一个[接口](https://tobebetterjavaer.com/oo/interface.html)吧;
84-
- 永久代是 HotSpot 虚拟机中对方法的一个实现,就像是接口的实现类;
85-
- Java 8 的时候,移除了永久代,取而代之的是元空间,是方法区的另外一个实现
146+
- 永久代是 HotSpot 虚拟机中对方法区的一个实现,就像是接口的实现类;
147+
- Java 8 的时候,移除了永久代,取而代之的是元空间,是方法区的另外一种实现,更灵活了
86148

87-
永久代是放在运行时数据区中的,所以它的大小受到 Java 虚拟机本身大小的限制,所以 Java 8 之前,会经常遇到 `java.lang.OutOfMemoryError: PremGen Space` 的异常,PremGen Space 就是方法区的意思;而元空间是直接放在内存中的,所以只受本机可用内存的限制,虽然也会发生内存溢出,但出现的几率相对之前就小了很多
149+
永久代是放在运行时数据区中的,所以它的大小受到 Java 虚拟机本身大小的限制,所以 Java 8 之前,会经常遇到 `java.lang.OutOfMemoryError: PremGen Space` 的异常,PremGen Space 就是方法区的意思;而元空间是直接放在内存中的,所以只受本机可用内存的限制。
88150

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

‎docs/string/immutable.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
2-
title: 为什么String是不可变的
3-
shortTitle: 为什么String是不可变的?
2+
title: 聊聊 Java 字符串,以及为什么String是不可变的
3+
shortTitle: 聊聊字符串以及为什么不可变
44
category:
55
- Java核心
66
tag:
@@ -12,6 +12,8 @@ head:
1212
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java字符串,String,不可变
1313
---
1414

15+
## 4.4 聊聊Java字符串,以及为什么String是不可变的?
16+
1517
我正坐在沙发上津津有味地读刘欣大佬的《码农翻身》——Java 帝国这一章,门铃响了。起身打开门一看,是三妹,她从学校回来了。
1618

1719
“三妹,你回来的真及时,今天我们打算讲 Java 中的字符串呢。”等三妹换鞋的时候我说。
@@ -24,6 +26,8 @@ head:
2426

2527
我应了一声后走到电脑桌前坐下来,顺手打开 Intellij IDEA,并找到了 String 的源码。
2628

29+
### 关于 String 类
30+
2731
```java
2832
public final class String
2933
implements java.io.Serializable, Comparable<String>, CharSequence {
@@ -42,10 +46,12 @@ public final class String
4246

4347
“第四,StringBuffer、StringBuilder 和 String 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到字符串拼接的时候就可以考虑一下 String 的另外两个好兄弟,StringBuffer 和 StringBuilder,它俩是可变的。”
4448

45-
“第五,Java 9 以前,[String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码](https://tobebetterjavaer.com/basic-extra-meal/jdk9-char-byte-string.html) Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。”
49+
“第五,Java 9 以前,String 是用 char 型数组实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码,可以戳[这篇了解详情](https://tobebetterjavaer.com/basic-extra-meal/jdk9-char-byte-string.html)这样做的好处是在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。”
4650

4751
“第六,每一个字符串都会有一个 hash 值,这个哈希值在很大概率是不会重复的,因此 String 很适合来作为 HashMap 的键值。”
4852

53+
### 为什么String不可变
54+
4955
“String 可能是 Java 中使用频率最高的引用类型了,因此 String 类的设计者可以说是用心良苦。”
5056

5157
比如说 String 的不可变性。

0 commit comments

Comments
 (0)
Please sign in to comment.