java - 用反射打破 JIT 优化

标签 java reflection java-8 jit

在为高度并发的单例类进行单元测试时,我偶然发现了以下奇怪的行为(在 JDK 1.8.0_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 关键字使代码输出正确的值。

抛开你对单例的同情(或缺乏同情),一分钟忘记使用这样的反射是在自找麻烦——我的假设是否正确,因为 JIT 优化是罪魁祸首?如果是这样 - 那些仅限于静态最终字段吗?

最佳答案

从字面上看你的问题,“......我的假设是否正确,因为 JIT 优化是罪魁祸首?”,答案是肯定的,在这个特定示例中,JIT 优化很可能对这种行为负责。
但自从改变static final字段完全不符合规范,还有其他事情可以类似地破坏它。例如。 JMM 没有定义此类更改的内存可见性,因此完全不确定其他线程是否或何时注意到此类更改。他们甚至不需要始终如一地注意到它,即他们可以使用新值,然后再次使用旧值,即使存在同步原语。
尽管如此,JMM 和优化器在这里无论如何都很难分开。
您的问题“……那些仅限于静态最终字段吗?”更难回答,因为优化当然不仅限于 static final领域,但行为,例如非静态final领域,是不一样的,理论和实践也有差异。
对于非静态 final字段,在某些情况下允许通过反射进行修改。这是由 setAccessible(true) 的事实表明的。足以使这种修改成为可能,而无需侵入 Field实例更改内部modifiers field 。
The specification说:

17.5.3. Subsequent Modification of final Fields

In some cases, such as deserialization, the system will need to change the final fields of an object after construction. final fields can be changed via reflection and other implementation-dependent means. The only pattern in which this has reasonable semantics is one in which an object is constructed and then the final fields of the object are updated. The object should not be made visible to other threads, nor should the final fields be read, until all updates to the final fields of the object are complete. Freezes of a final field occur both at the end of the constructor in which the final field is set, and immediately after each modification of a final field via reflection or other special mechanism.

Another problem is that the specification allows aggressive optimization of final fields. Within a thread, it is permissible to reorder reads of a final field with those modifications of a final field that do not take place in the constructor.

Example 17.5.3-1. Aggressive Optimization of final Fields
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 
    } 
}

In the d method, the compiler is allowed to reorder the reads of x and the call to g freely. Thus, new A().f() could return -1, 0, or 1.



在实践中,确定可以在不违反上述法律情况的情况下进行积极优化的正确位置是 an open issue ,所以除非 -XX:+TrustFinalNonStaticFields已指定,HotSpot JVM 不会优化非静态 final字段与 static final 相同领域。
当然,当您不将字段声明为 final 时,JIT 不能假设它永远不会改变,但是,在没有线程同步原语的情况下,它可能会考虑它优化的代码路径(包括反射路径)中发生的实际修改。所以它可能仍然积极地优化访问,但只是在执行线程内的程序顺序中仍然发生读取和写入。因此,只有在没有适当同步构造的情况下从不同线程查看它时,您才会注意到优化。

关于java - 用反射打破 JIT 优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59633108/

相关文章:

java - spring security 自定义 AuthenticationProvider 被调用两次并失败

java - 创建具有特定 session ID 的 session

c++ - 有没有办法找到任意类类型的直接基类(如果有的话)?

C# 反射,将 MakeGenericMethod 与具有 'new()' 类型约束的方法一起使用

java - Android 数据保护程序关闭时获取 "BLOCKED"网络状态

java - IntelliJ Idea 需要很长时间才能启动(JetBrains CertificateException)

c# - 强制 `Expression<Func<TInstance,TProp>>` 有一个 Body 是 `MemberExpression`

java - Java 8 更新后的 RC4 相关问题

java - 使用 Java 8 Time API 获取第一次和最后一次时间(以毫秒为单位)

Java - ZonedDateTime 没有正确转换为 Date 对象?