Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/359.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 字符串文本、插入和反射_Java_String_Reflection_Jvm - Fatal编程技术网

Java 字符串文本、插入和反射

Java 字符串文本、插入和反射,java,string,reflection,jvm,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")); } catc

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

我不明白为什么不打印
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));
    }
}
当然,由于字符串插入,正在修改的
实例“true”
PrintStream
print
方法中使用的实例完全相同

public void print(boolean b) {
    write(b ? "true" : "false");
}
我错过了什么

编辑

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

System.out.println(true);
尝试之前,输出为

true
false

我写这篇文章是作为一个社区维基,因为我不知道它是否正确,也不理解其中的细节

出现的情况是,当在运行时遇到字符串文本时,JVM会检查字符串池(使用
equals
)以查看字符串是否已经存在。如果不存在,则使用新实例。此对象(新对象或已在字符串池中的对象)是从现在起将用于该类中所有相同的字符串文本的对象

考虑这个例子:

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) {
        System.out.println(true);       // 1
        new MyClass();
        System.out.println(true);       // 2
        System.out.println("true");     // 3
        printTrue();
        OtherClass.printTrue();
    }

    public static void printTrue() {
        System.out.println("true");     // 4
    }
}

public class OtherClass {

    static void printTrue() {
        System.out.println("true");     // 5
    }
}
这张照片是:

正确



真的

我的解释是:

在第1行中,JVM在
PrintStream
类中遇到文本
“true”
。将向池中添加一个新字符串。然后调用
newmyclass()
。在这个构造函数中,JVM在
MyClass
类中遇到字符串literal
“true”
。这个字符串已经在池中,因此池中的实例将被使用,但至关重要的是,它也是稍后在第3行和第4行中使用的实例。然后修改支持此字符串的数组。因此,第2、3和4行都打印
false
。接下来,调用
OtherClass.printTrue()
,JVM在
OtherClass
中第一次遇到字符串literal
“true”
。此字符串与池中的字符串不相等,因为池中的字符串现在具有支持数组
[f,a,l,s,e]
。因此,将使用一个新的字符串实例,并在第5行打印
true

现在假设我们注释掉第1行:

//        System.out.println(true);       // 1
这一次的输出是:

正确


真的


为什么第2行产生不同的结果?这里的区别是,在修改备份数组之前,
PrintStream
类中不会遇到文本
“true”
。因此,“错误”字符串不是
PrintStream
类中使用的字符串。但是,第3行和第4行继续打印“false”
,原因与上面相同。

这可能是一个热点JVM错误

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

  • java.lang.String
    字符串文本的实例是在常量池解析期间延迟创建的
  • 最初,字符串文本在常量池中由指向的结构表示
  • 每个类都有自己的常量池。也就是说,
    MyClass
    PrintStream
    有自己的一对
    CONSTANT\u String\u info
    /
    CONSTANT\u Utf8\u info
    cpool条目,用于文本“true”
  • 当第一次访问
    CONSTANT\u String\u info
    时,JVM启动解析过程。字符串实习是这个过程的一部分
  • 为了找到一个匹配被插入的文本,JVM将
    CONSTANT\u Utf8\u info
    的内容与
    StringTable
    中字符串实例的内容进行比较
  • ^^^这就是问题所在。将来自cpool的原始UTF数据与Java
    char[]
    数组内容进行比较,用户可以通过反射伪造这些内容
那么,您的测试中发生了什么?

  • f.set(“true”,f.get(“false”)
    MyClass
    中启动文本“true”的解析
  • JVM在
    StringTable
    中没有发现与序列“true”匹配的实例,并创建一个新的
    java.lang.String
    ,它存储在
    StringTable
  • 通过反射替换
    StringTable
    中该字符串的
  • System.out.println(true)
    PrintStream
    类中启动文本“true”的解析
  • JVM将UTF序列“true”与
    StringTable
    中的字符串进行比较,但未找到匹配项,因为该字符串已具有“false”值。“true”的另一个字符串被创建并放置在
    StringTable
  • 为什么我认为这是一个bug?

    并要求包含相同字符序列的字符串文本必须指向
    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!");
            }
        }
    }
    

    JVM的一个可能修复方法是将指向原始UTF序列的指针与
    java.lang.String
    对象一起存储在
    StringTable
    中,这样内部进程就不会将cpool数据(用户无法访问)与
    数组(可通过反射访问)进行比较了。

    @Tunaki谢谢,您让我放心了。在我发布一个问题之前,我总是很紧张,因为我问了一个愚蠢的问题,我会被大量的反对票击中。还有一个额外的转折点:如果你在设置字段之前做了
    System.out.println(true)
    ,那么它就会像我们预期的那样工作。基本上,编写器似乎有自己的缓存或字符串。@yshavit,JVM如何知道它在池中?;)它只能使用
    hashCode
    equals
    。第一个是一样的,第二个不是。这可能是一个解释(不确定,这里也有点晚了:D)哦,别忘了。这真的很有趣--打破东西看看里面有什么。信息技术