更改JIT编译的最终值

时间:2016-01-16 16:46:38

标签: java reflection

我注意到一个非常奇怪的事情,通过Reflection更改最终字段后,返回该字段的方法始终给出旧值。我想这可能是因为JIT编译器。

以下是示例程序:

public class Main
{
private static final Main m = new Main();
public static Main getM()
{
    return m;
}

public static void main(String args[]) throws Exception
{
    Main m = getM();
    int x = 0;
    for(int i = 0;i<10000000;i++)
    {
        if(getM().equals(m))
            x ++;
    }
    Field f = Main.class.getDeclaredField("m");
    f.setAccessible(true);
    removeFinal(f);
    Main main1 = new Main();
    f.set(null, main1);
    Main main2 = (Main) f.get(null);
    Main main3 = getM();
    System.out.println(main1.toString());
    System.out.println(main2.toString());
    System.out.println(main3.toString());
}

private static void removeFinal(Field field) throws NoSuchFieldException, IllegalAccessException
{
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
}

结果是:

Main@1be6f5c3
Main@1be6f5c3
Main@6b884d57

我想知道,我如何让getM()返回更新值?

2 个答案:

答案 0 :(得分:12)

  

我想知道,我如何让getM()返回更新值?

决赛时,你不能。返回“旧”值是合法行为,如JLS 17.5.3

  

即使这样,也有许多并发症。如果是最终字段   在字段中初始化为常量表达式(第15.28节)   声明,可能无法观察到对最终字段的更改,因为   在编译时将该最终字段的使用替换为该值   常数表达式。

     

另一个问题是规范允许攻击性   最终字段的优化。在一个帖子中,允许   使用final的修改重新排序最终字段的读取   不在构造函数中发生的字段。

请参阅该章中包含的指导性示例。

尝试克服这一规定必须包括在堆栈中弄乱优化器,并且充其量是脆弱的。如果您选择修改字段,那么根据定义,这些字段不应该是最终字段。如果你出于性能原因想要这个(你真的吗?),那么JSR 292提供了执行"almost final"构造的机制。

答案 1 :(得分:1)

如果您真的想要更改该值,可以包装该类并覆盖getter。

也许您已经听说过“委托”/“代理”模式。