`
jaesonchen
  • 浏览: 310009 次
  • 来自: ...
社区版块
存档分类
最新评论

Java中的字符串常量池

 
阅读更多

Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式。然而这两种实现其实存在着一些性能和内存占用的差别。这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池。

工作原理

当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。

举例说明

字面量创建形式

1
String str1 = "droid";

JVM检测这个字面量,这里我们认为没有内容为droid的对象存在。JVM通过字符串常量池查找不到内容为droid的字符串对象存在,那么会创建这个字符串对象,然后将刚创建的对象的引用放入到字符串常量池中,并且将引用返回给变量str1。

如果接下来有这样一段代码

1
String str2 = "droid";

同样JVM还是要检测这个字面量,JVM通过查找字符串常量池,发现内容为”droid”字符串对象存在,于是将已经存在的字符串对象的引用返回给变量str2。注意这里不会重新创建新的字符串对象。

验证是否为str1和str2是否指向同一对象,我们可以通过这段代码

1
System.out.println(str1 == str2);

结果为true

使用new创建

1
String str3 = new String("droid");

当我们使用了new来构造字符串对象的时候,不管字符串常量池中有没有相同内容的对象的引用,新的字符串对象都会创建。因此我们使用下面代码测试一下,

1
2
String str3 = new String("droid");
System.out.println(str1 == str3);

结果如我们所想,为false,表明这两个变量指向的为不同的对象。

intern

对于上面使用new创建的字符串对象,如果想将这个对象的引用加入到字符串常量池,可以使用intern方法。

调用intern后,首先检查字符串常量池中是否有该对象的引用,如果存在,则将这个引用返回给变量,否则将引用加入并返回给变量。

1
2
String str4 = str3.intern();
System.out.println(str4 == str1);

输出的结果为true

疑难问题

前提条件?

字符串常量池实现的前提条件就是Java中String对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。

引用 or 对象

字符串常量池中存放的时引用还是对象,这个问题是最常见的。字符串常量池存放的是对象引用,不是对象。在Java中,对象都创建在堆内存中

更新验证,收到的很多评论也在讨论这个问题,我简单的进行了验证。 验证环境

1
2
3
4
5
6
7
8
9
10
11
12
13
22:18:54-androidyue~/Videos$ cat /etc/os-release
NAME=Fedora
VERSION="17 (Beefy Miracle)"
ID=fedora
VERSION_ID=17
PRETTY_NAME="Fedora 17 (Beefy Miracle)"
ANSI_COLOR="0;34"
CPE_NAME="cpe:/o:fedoraproject:fedora:17"
22:19:04-androidyue~/Videos$ java -version
java version "1.7.0_25"
OpenJDK Runtime Environment (fedora-2.3.12.1.fc17-x86_64)
OpenJDK 64-Bit Server VM (build 23.7-b01, mixed mode)

验证思路:以下的Java程序读取一个大小为82M的视频文件,以字符串形式进行intern操作。

1
2
22:01:17-androidyue~/Videos$ ll -lh | grep why_to_learn.mp4
-rw-rw-r--. 1 androidyue androidyue  82M Oct 20  2013 why_to_learn.mp4

验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class TestMain {
  private static String fileContent;
  public static void main(String[] args) {
      fileContent = readFileToString(args[0]);
      if (null != fileContent) {
          fileContent = fileContent.intern();
          System.out.println("Not Null");
      }
  }
  private static String readFileToString(String file) {
      BufferedReader reader = null;
      try {
          reader = new BufferedReader(new FileReader(file));
          StringBuffer buff = new StringBuffer();
          String line;
          while ((line = reader.readLine()) != null) {
              buff.append(line);
          }
          return buff.toString();
      } catch (FileNotFoundException e) {
          e.printStackTrace();
      } catch (IOException e) {
          e.printStackTrace();
      } finally {
          if (null != reader) {
              try {
                  reader.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
      return null;
  }
}

由于字符串常量池存在于堆内存中的永久代,适用于Java8之前。我们通过设置永久代一个很小的值来进行验证。如果字符串对象存在字符串常量池中,那么必然抛出java.lang.OutOfMemoryError permgen space错误。

1
java -XX:PermSize=6m TestMain ~/Videos/why_to_learn.mp4

运行证明程序没有抛出OOM,其实这个不能很好的证明存储的是对象还是引用。

但是这个至少证明了字符串的实际内容对象char[]不存放在字符串常量池中。既然这样的话,其实字符串常量池存储字符串对象还是字符串对象的引用反而不是那么重要。但个人还是倾向于存储的为引用。

优缺点

字符串常量池的好处就是减少相同内容字符串的创建,节省内存空间。

如果硬要说弊端的话,就是牺牲了CPU计算时间来换空间。CPU计算时间主要用于在字符串常量池中查找是否有内容相同对象的引用。不过其内部实现为HashTable,所以计算成本较低。

GC回收?

因为字符串常量池中持有了共享的字符串对象的引用,这就是说是不是会导致这些对象无法回收?

首先问题中共享的对象一般情况下都比较小。据我查证了解,在早期的版本中确实存在这样的问题,但是随着弱引用的引入,目前这个问题应该没有了。

关于这个问题,可以具体了解这片文章interned Strings : Java Glossary

intern使用?

关于使用intern的前提就是你清楚自己确实需要使用。比如,我们这里有一份上百万的记录,其中记录的某个值多次为美国加利福尼亚州,我们不想创建上百万条这样的字符串对象,我们可以使用intern只在内存中保留一份即可。关于intern更深入的了解请参考深入解析String#intern

总有例外?

你知道下面的代码,会创建几个字符串对象,在字符串常量池中保存几个引用么?

1
String test = "a" + "b" + "c";

答案是只创建了一个对象,在常量池中也只保存一个引用。我们使用javap反编译看一下即可得知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
17:02 $ javap -c TestInternedPoolGC
Compiled from "TestInternedPoolGC.java"
public class TestInternedPoolGC extends java.lang.Object{
public TestInternedPoolGC();
  Code:
   0:  aload_0
   1:  invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:  return
public static void main(java.lang.String[])   throws java.lang.Exception;
  Code:
   0:  ldc  #2; //String abc
   2:  astore_1
   3:  return

看到了么,实际上在编译期间,已经将这三个字面量合成了一个。这样做实际上是一种优化,避免了创建多余的字符串对象,也没有发生字符串拼接问题。关于字符串拼接,可以查看Java细节:字符串的拼接

分享到:
评论

相关推荐

    第4节: 揭秘JVM字符串常量池和Java堆-01

    第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: 揭秘JVM字符串常量池和Java堆-01第4节: ...

    什么是字符串常量池?Java开发Java经验技巧共6页.p

    如果另一个地方也创建了同样的字符串,例如`String str2 = "Hello"`,Java会检查字符串常量池中是否已经有了"Hello",如果有,则不会创建新的对象,而是直接引用已存在的。 字符串常量池位于方法区的运行时常量池...

    Java String 字符串常量池解析

    它维护着一个表,总是为池中每个唯一的字符串对象维护一个引用,这意味着它们一直引用着字符串常量池中的对象,因此,在常量池中的这些字符串不会被垃圾收集器回收。 操作字符串常量池的方式有多种,例如使用 `...

    Java 中的字符串常量池详解

    - **字符串常量池中存储的是引用还是对象**:字符串常量池中存放的是对象的引用,而不是对象本身。对象仍然存储在Java堆内存中。这可以通过`intern()`方法的使用和内存分析来验证。 - **字符串的不可变性**:Java中...

    Java中的字符串常量池详细介绍

    关于字符串常量池中存储的是引用还是对象的问题,实际上,Java的字符串常量池存放的是对象的引用,而不是对象本身。对象仍然在堆内存中创建。这一点可以通过实验验证,例如通过读取大文件并执行`intern()`操作,观察...

    字符数组的存储方式 字符串常量池.docx

    当使用双引号定义字符串时,JVM会首先检查字符串常量池中是否存在该字符串。如果不存在,它会在池中添加这个字符串,并在堆中创建一个对应的String对象,这个对象底层是通过char数组实现的。如果常量池中已经存在该...

    8 StringTable(字符串常量池).md,学习代码

    字符串常量池中的字符串是不可变的,而堆内存中的字符串对象可以被修改。例如,`String str3 = new String("hello"); str3 += "world";`,这里`str3`最初指向的是堆内存的一个字符串,修改后指向新的字符串对象。 #...

    java 创建字符串类

    - 字符串常量池是JVM内存中的一个特殊区域,用于存放所有的字符串字面量。当创建一个`String`对象时,如果常量池中已经存在相同内容的字符串,那么将返回该字符串的引用,而不是创建新的对象。 4. **字符串比较**...

    string常量池和intern_韩雅茹Java系列2021.pdf

    在Java中,字符串是不可变的,可以共享运行时实例创建的全局字符串常量池中。字符串常量池维护一个引用表,每个唯一的字符串对象都有一个对应的引用。这样,字符串常量池中的字符串不会被垃圾收集器回收。 字符串...

    理解java中的字符串的内存分配

    这里,由于 `"abc"` 是一个字符串字面量,Java 会直接在字符串常量池中查找或创建 `"abc"` 对象 `B`。然后,`s2` 引用将直接指向字符串常量池中的 `B`。 接下来: ```java String s3 = "abc"; ``` 当再次创建 `...

    java内存分配之常量池,栈,堆1

    在运行时,如果两个字符串的值相等,它们在常量池中可能只有一份拷贝,这是Java字符串常量池复用机制的体现。在Java 6及之前,字符串常量池位于永久代,从Java 7开始移到了堆中。 栈和堆之间的区别在于,栈中的数据...

    C#之CLR内存字符串常量池(string)

    这就是为什么在IL(中间语言)代码中,我们看到的是`ldstr`指令,而不是`newObj`,因为`ldstr`加载的是已存在于字符串常量池中的字符串,而不是创建新的对象。 以提供的示例代码为例: ```csharp class Program { ...

    java实现文件的压缩解压

    根据给定的信息,我们可以深入探讨Java中文件的压缩与解压缩技术,同时解析字符串内部的工作原理,特别是关于字符串常量池的重要概念。 ### Java中文件的压缩与解压缩 #### 压缩技术概述 在Java中,可以利用标准库...

    深入解析JVM之内存结构及字符串常量池(推荐)

    在JVM中,字符串常量池的实现是通过String.intern()方法来实现的,该方法可以将字符串常量加入字符串常量池中,以便重复使用。字符串常量池的使用可以减少内存占用,提高程序的执行效率。 JVM的内存结构是非常复杂...

    有关Java中字符串的几个问题的讨论

    字符串常量池是一个特殊的存储区域,用于存放字符串字面量。使用双引号创建的字符串会直接放入池中,避免了重复对象的创建。例如: ```java String str3 = "Hello"; String str4 = "Hello"; System.out.println...

    Java语言中字符串常量和变量的分析与比较.pdf

    后者在内存中,如果常量池中已存在相同内容的字符串,会直接引用常量池中的对象,避免内存浪费。 - 不可变性:`String`对象一旦创建,其内容就不能修改。例如,`String s = "abc"; s = "abcdef"`,这里实际上是创建...

    深入理解 Java String#intern() 内存模型.docx

    Java String#intern() 内存模型是 Java 语言中一个重要的概念,.string#intern() 方法是 Java 字符串常量池中一个重要的组件。字符串常量池是一个固定大小的 HashMap,桶的数量默认是 1009,从 Java7u40 开始,该...

    java中常用字符串方法总结

    Java的字符串字面量会被放入字符串常量池中,如果两个字符串字面量内容相同,它们会指向同一个对象。例如,`str2`与`"World"`在内存中是共享的。 3. **字符串比较** `equals()`方法用于比较字符串内容是否相等,...

    解析Java中的String对象的数据类型 字符串

    在Java中,如果我们使用字面值方式创建的String对象,它们是常量池中的字符串常量,如果我们使用new关键字创建的String对象,它们是运行时创建的新对象。例如,String s0="kvill";,String s1=new String("kvill");...

    关于JAVA中的字符串的比较

    在这个例子中,由于`str1`和`str2`都直接赋值为"java",JVM会确保它们指向同一个字符串常量池中的对象,因此使用`==`进行比较返回`true`。 #### 使用`new`关键字创建字符串 当使用`new`关键字创建字符串对象时,...

Global site tag (gtag.js) - Google Analytics