字符串文字,实习和反思

Pau*_*ton 10 java string reflection jvm

我正试图找到这个问题的第三个解决方案.

我不明白为什么这不打印false.

public class MyClass {

    public MyClass() {
        try {
            Field f = String.class.getDeclaredField("value");
            f.setAccessible(true);
            f.set("true", f.get("false"));
        } catch (Exception e) {
        }
    }

    public static void main(String[] args) {
        MyClass m = new MyClass();
        System.out.println(m.equals(m));
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,由于字符串实习,"true"被修改的实例与print方法中使用的实例完全相同PrintStream

public void print(boolean b) {
    write(b ? "true" : "false");
}
Run Code Online (Sandbox Code Playgroud)

我错过了什么?

编辑

@yshavit的一个有趣的观点是,如果你添加这条线

System.out.println(true);
Run Code Online (Sandbox Code Playgroud)

之前try,输出是

true
false
Run Code Online (Sandbox Code Playgroud)

apa*_*gin 6

这可以说是一个HotSpot JVM错误.

问题出在字符串文字实习机制中.

  • java.lang.String 字符串文字的实例是在常量池解析期间懒惰创建的.
  • 最初,字符串文字通过CONSTANT_String_info指向的结构在常量池中表示CONSTANT_Utf8_info.
  • 每个类都有自己的常量池.也就是说,MyClass并为文字'true'PrintStream设置了自己的CONSTANT_String_info/ CONSTANT_Utf8_infocpool条目对.
  • CONSTANT_String_info第一次访问时,JVM启动解析过程.字符串实习是此过程的一部分.
  • 要查找被实际文字的匹配项,JVM会将内容CONSTANT_Utf8_info与字符串实例的内容进行比较StringTable.
  • ^^^这是问题所在.将来自cpool的原始UTF数据与可由char[]用户通过Reflection欺骗的Java 数组内容进行比较.

那么,你的测试中发生了什么?

  1. f.set("true", f.get("false"))启动文字"真实"的解决方案MyClass.
  2. JVM在StringTable匹配序列'true'时没有发现任何实例,并创建一个java.lang.String存储在其中的新实例StringTable.
  3. valueStringTable通过Reflection替换了String from .
  4. System.out.println(true)在课堂上启动文字'true'的解析PrintStream.
  5. JVM将UTF序列'true'与Strings from 进行比较StringTable,但没有找到匹配项,因为该String已经有'false'值.另一个'true'字符串被创建并放入StringTable.

为什么我认为这是一个错误?

JLS§3.10.5JVMS§5.1需要包含相同的字符序列必须指向的同一个实例是字符串文字java.lang.String.

但是,在以下代码中,具有相同字符序列的两个字符串文字的分辨率会导致不同的实例.

public class Test {

    static class Inner {
        static String trueLiteral = "true";
    }

    public static void main(String[] args) throws Exception {
        Field f = String.class.getDeclaredField("value");
        f.setAccessible(true);
        f.set("true", f.get("false"));

        if ("true" == Inner.trueLiteral) {
            System.out.println("OK");
        } else {
            System.out.println("BUG!");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

JVM的一个可能的修复方法是将指向原始UTF序列的指针StringTablejava.lang.String对象一起存储,这样实习过程就不会将cpool数据(用户不可访问)与value数组(可通过Reflection访问)进行比较.