Java中字符串池的基础机制?

Sai*_*S.R 23 java string performance jvm pool

我很好奇,为什么字符串可以在不调用创建new String(),作为API提到它是Objectclass java.lang.String

那么我们如何才能使用String s="hi"而不是String s=new String("hi")

这篇文章澄清了==运算符的使用和缺席,new 并说这是由于String文字被实际内容或从文字池中获取JVM,因此Strings是不可变的.

看到诸如此类的陈述

String s="hi"
Run Code Online (Sandbox Code Playgroud)

第一次真正发生了什么?

  1. 是否JVM像这样替换它String s=new String("hi"),其中创建了一个Object并将"hi"其添加到String文本池中,因此后续调用如String s1="hi"从池中获取?

  2. 这是底层机制的运作方式吗?如果是的话,那就是

    String s=new String("Test");
    String s1="Test";
    
    Run Code Online (Sandbox Code Playgroud)

    同样的

    String s="Test";
    String s1="Test";
    
    Run Code Online (Sandbox Code Playgroud)

    在内存利用率和效率方面

  3. 另外,有什么方法可以访问字符串池来检查它中有多少String文字,占用了多少空间等等?

Jes*_*per 15

Java编译器对字符串文字有特殊支持.假设没有,那么在源代码中创建字符串真的很麻烦,你必须写下这样的东西:

// Suppose that we would not have string literals like "hi"
String s = new String(new char[]{ 'h', 'i' });
Run Code Online (Sandbox Code Playgroud)

回答你的问题:

  1. 或多或少,如果你真的想知道细节,你必须研究JVM的源代码,你可以在OpenJDK找到它,但要注意它是巨大而复杂的.

  2. 不,这两个不相同.在第一种情况下,您将显式创建一个新String对象:

    String s=new String("Test");
    
    Run Code Online (Sandbox Code Playgroud)

    它将包含String由文字表示的对象的副本"Test".请注意,用Java 编写绝不是一个好主意new String("some literal")- 字符串是不可变的,并且永远不需要复制字符串文字.

  3. 我不知道检查字符串池中的内容.

  • `return string.intern()== string`应检查字符串是否被实现.这是一个肮脏的黑客,但应该回答正确的答案.大多数时候.它还具有实现`String`的副作用. (2认同)

The*_*ind 10

  1. String s="hi" 第一次真正发生了什么?

JVM是否像这样替换它String s=new String("hi"),其中创建了一个Object并且"hi"被添加到String文本池中,因此后续调用如String s1 ="hi"从池中获取?

不会.真正发生的事情是 - 字符串文字在编译期间被解析,并且在加载/初始化懒惰加密(添加到字符串常量池).因此,它们可供JVM中的类使用.请注意,即使在字符串常量池中有一个带有值的String,也会在堆上创建另一个String并返回其引用."hi"new String("hi")

 String s=new String("Test"); 
 String s1="Test"; 
Run Code Online (Sandbox Code Playgroud)

同样的

 String s="Test"; 
 String s1="Test"; 
Run Code Online (Sandbox Code Playgroud)

在内存利用率和效率方面?

不,在第一种情况下,创建2个"测试"字符串.一个将被添加到String常量池(假设它不存在)和另一个在堆上.第二种情况可以是GCed.在第二种情况下,String常量池中只有一个字符串文字,并且有2个引用它(ss1).

  1. 另外,如果有任何方法我们可以访问字符串池,检查它中存在多少字符串文字,空间占用等来自程序或任何监视工具?

我认为我们不能看到String常量池的内容.我们只能基于我们的假设来假设确认行为.

  • 但是请注意,添加到文字池是由编译器(javac)完成的编译时操作,并且在运行时由JVM调用字符串构造函数.所以实际上它是相同的机制,就在调用新的String("test")时,你将文本池中的引用作为构造函数arg传递而不是直接赋值.http://docs.oracle.com/javase/8/docs/api/java/lang/String.html#intern--上还有#intern方法,它将删除重复的实例并将其替换为池中的引用 (2认同)
  • @saberduck我略微不同意第一句话.编译器将String添加到类的常量池中,稍后JVM将所有类Strings添加到其String池(可能在类加载时一次全部). (2认同)

sup*_*cat 7

以下是略微简化,因此不要试图引用它的确切细节,但一般原则适用.

每个编译的Java类都包含一个数据blob,它指示在该类文件中声明了多少字符串,每个字符串的长度以及属于所有字符串的字符.加载类时,类加载器将创建一个String[]合适的大小来保存该类中定义的所有字符串; 对于每个字符串,它将生成一个char[]合适的大小,从类文件中读取适当数量的字符到其中char[],创建一个String封装这些字符,并将引用存储到类中String[].

在编译某个类(例如Foo)时,编译器知道它遇到的第一个,第二个,第三个,第五个等myString = "George";字符串文字.如果代码说并且George是第六个字符串文字,那么它将在代码中显示为"加载字符串文字#6" "指导; 即时编译器在为该指令生成代码时,将生成一条指令以获取与该类关联的第六个字符串引用.


kla*_*rki 6

这与主题没有密切关系,但每当你怀疑java编译器会做什么时,你可以使用

javap -c CompiledClassName
Run Code Online (Sandbox Code Playgroud)

打印实际发生的事情.(来自dir的CompiledClassName,其中CompiledClassName.class是)

要添加到Jesper的答案,有更多的机制在起作用,比如当你从文字或最终变量连接一个字符串时,它仍将使用实习池:

String s0 = "te" + "st";
String s1 = "test";
final String s2 = "te";
String s3 = s2 + "st";
System.out.println(s0==s1); //true
System.out.println(s3==s1); //true
Run Code Online (Sandbox Code Playgroud)

但是当你使用非final变量连接时,它将不使用池:

String s0 = "te";
String s1 = s0 + "st";
String s2 = "test";
System.out.println(s1 == s2); //false
Run Code Online (Sandbox Code Playgroud)


apa*_*gin 5

  1. 一种,但不完全是.
    常量池解析期间创建并实现字符串常量.这在第一次执行LDC加载字符串文字的字节码时发生.在第一次执行之后,JVM将JVM_CONSTANT_UnresolvedString常量池标记替换为JVM_CONSTANT_String标记,以便下次LDC将使用现有字符串而不是创建新字符串.

  2. "Test"不会.第一次使用会创建一个新的字符串对象.然后new String("Test")将创建第二个对象.

  3. 是的,使用HotSpot Serviceability Agent.这是一个例子.