当前位置: 首页 > 知识库问答 >
问题:

通过反射打破JIT优化

司马英才
2023-03-14

在处理高度并发的单例类的单元测试时,我偶然发现了以下奇怪的行为(在JDK 1.8.0\U 162上测试):

private static class SingletonClass {
    static final SingletonClass INSTANCE = new SingletonClass(0);
    final int value;

    static SingletonClass getInstance() {
        return INSTANCE;
    }

    SingletonClass(int value) {
        this.value = value;
    }
}

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

    System.out.println(SingletonClass.getInstance().value); // 0

    // Change the instance to a new one with value 1
    setSingletonInstance(new SingletonClass(1));
    System.out.println(SingletonClass.getInstance().value); // 1

    // Call getInstance() enough times to trigger JIT optimizations
    for(int i=0;i<100_000;++i){
        SingletonClass.getInstance();
    }

    System.out.println(SingletonClass.getInstance().value); // 1

    setSingletonInstance(new SingletonClass(2));
    System.out.println(SingletonClass.INSTANCE.value); // 2
    System.out.println(SingletonClass.getInstance().value); // 1 (2 expected)
}

private static void setSingletonInstance(SingletonClass newInstance) throws NoSuchFieldException, IllegalAccessException {
    // Get the INSTANCE field and make it accessible
    Field field = SingletonClass.class.getDeclaredField("INSTANCE");
    field.setAccessible(true);

    // Remove the final modifier
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    // Set new value
    field.set(null, newInstance);
}

main()方法的最后两行在INSTANCE的值上不一致-我猜JIT完全摆脱了该方法,因为该字段是静态final。删除final关键字可以使代码输出正确的值。

撇开你对单例的同情(或缺乏同情)不谈,暂时忘记像这样使用反射是在自找麻烦——我的假设是正确的吗?JIT优化是罪魁祸首?如果是这样的话,那些仅限于静态的最终领域吗?

共有1个答案

贺高杰
2023-03-14

从字面上理解你的问题,“......我的假设是正确的,因为JIT优化是罪魁祸首吗?”,答案是肯定的,在这个特定示例中,JIT优化很可能对这种行为负责。

但是,由于更改静态final字段完全不符合规范,因此还有其他类似的事情可以破坏它。E、 g.JMM没有定义这种更改的内存可见性,因此,其他线程是否或何时注意到这种更改是完全不确定的。他们甚至不需要一致地注意到它,即他们可以使用新值,然后再次使用旧值,即使在存在同步原语的情况下也是如此。

尽管如此,这里很难将JMM和优化器分开。

您的问题“…这些仅限于静态最终字段吗?”很难回答,因为优化当然不限于静态最终字段,但非静态最终字段的行为(例如非静态最终字段)是不同的,并且在理论和实践之间也存在差异。

对于非静态的final字段,在某些情况下允许通过反射进行修改。这一点可以通过以下事实来说明,即设置可访问性(true)足以使此类修改成为可能,而无需侵入字段实例来更改内部的修改器字段。

规范规定:

在某些情况下,如反序列化,系统需要在构建后更改对象的final字段<可以通过反射和其他依赖于实现的方式更改代码>最终字段。唯一具有合理语义的模式是构建对象,然后更新对象的最终字段。在对象的最终字段的所有更新完成之前,不应使对象对其他线程可见,也不应读取最终字段。在设置了最终字段的构造函数末尾,以及通过反射或其他特殊机制对最终字段进行每次修改后,都会立即冻结最终字段。

另一个问题是,该规范允许对最终字段进行积极的优化。在一个线程中,允许使用构造函数中未发生的对最终字段的修改来重新排序最终字段的读取。

class A {
    final int x;
    A() { 
        x = 1; 
    } 

    int f() { 
        return d(this,this); 
    } 

    int d(A a1, A a2) { 
        int i = a1.x; 
        g(a1); 
        int j = a2.x; 
        return j - i; 
    }

    static void g(A a) { 
        // uses reflection to change a.x to 2 
    } 
}

d方法中,允许编译器自由地重新排序x的读取和对g的调用。因此,new A(). f()可以返回-101

在实践中,确定在不违反上述法律场景的情况下可以进行积极优化的正确位置是一个悬而未决的问题,因此除非指定了-XX: TrustFinalNonStaticFields,否则HotSpot JVM不会优化非静态最终字段与静态最终字段相同的方式。

当然,当您没有将字段声明为final时,JIT不能假定它永远不会更改,尽管在没有线程同步原语的情况下,它可能会考虑在其优化的代码路径中发生的实际修改(包括反射的修改)。因此,它仍然可以积极地优化访问,但前提是读取和写入仍然按照执行线程中的程序顺序进行。因此,只有在没有适当的同步构造的情况下,从不同的线程查看优化时,您才会注意到它。

 类似资料:
  • 如果反射值对象(reflect.Value)中值的类型为函数时,可以通过 reflect.Value 调用该函数。使用反射调用函数时,需要将参数使用反射值对象的切片 []reflect.Value 构造后传入 Call() 方法中,调用完成时,函数的返回值通过 []reflect.Value 返回。 下面的代码声明一个加法函数,传入两个整型值,返回两个整型值的和。将函数保存到反射值对象(refle

  • 问题内容: 在有关Hotspot中的JIT的讲座中,我想提供尽可能多的JIT执行的特定优化示例。 我只知道“方法内联”,但是应该更多。为每个示例投票。 问题答案: 好吧,您应该阅读Brian Goetz的文章以获取示例。 简而言之,HotSpot可以并且将: 内联方法 连接同一对象上的相邻块 如果无法从其他线程访问监视器,则消除锁定 消除无效代码(因此大多数微基准都是毫无意义的) 对非变量的直接存

  • 本文向大家介绍C#通过反射打开相应窗体方法分享,包括了C#通过反射打开相应窗体方法分享的使用技巧和注意事项,需要的朋友参考一下 C#单击菜单栏或工具栏时通过反射打开窗体的方法,可以以取代长长的if-else或switch-case语句。要点:将菜单或工具栏项的名称设置为与相应窗体名称相同(关键)。 如果我们的一个窗体里有N个窗体,或者是有一Tree需要对应的调用N个窗体话,如果使用IF ELse方

  • 主要内容:使用反射值对象包装任意值,从反射值对象获取被包装的值当我们将一个接口值传递给一个 reflect.ValueOf 函数调用时,此调用返回的是代表着此接口值的动态值的一个 reflect.Value 值。我们必须通过间接的途径获得一个代表一个接口值的 reflect.Value 值。 reflect.Value 类型有很多方法( https://golang.google.cn/pkg/reflect/)。我们可以调用这些方法来观察和操纵一个 ref

  • 要 动态获取一个对象方法的信息,首先需要通过下列方法之一创建一个 类型的对象或者数组。 getMethods() getMethods(String name,Class<?> …parameterTypes) getDeclaredMethods() getDeclaredMethods(String name,Class<?>...parameterTypes) 如果是访问指定的构造方法,需要

  • 问题内容: 我一直在尝试找出能做什么或可能有什么好处,但同时我想知道是否可以通过反射投射对象。 首先,我认为以下几行可能会错误地起作用: 但是,如果没有显式强制转换,它将无法正常工作。 那么什么是上课方法呢?并且仅通过反射来投射对象是否有可能,因此您找到了该对象的类,对其进行使用并以某种方式对其进行了投射? 问题答案: 一个有效的示例: 这使您可以编写: 您的代码不起作用的原因是Class.for