System.out 和 final 对象内联

3
众所周知,javac在编译时会内联常量,这可能会导致有趣的问题,特别是当只重新编译应用程序的某些部分时。大家都知道解决方法(让javac不理解它实际上是一个编译时常量)。
但如果我们查看oracle JDK中System.class的代码,我们会看到以下内容:
public final static PrintStream out = null;

因此,可以推断出out是通过反射或JVM初始化中的其他机制设置的。但是这引出了一个问题:为什么这里没有内联out?(至少我使用System.out.println()时从未收到过空指针异常)。
javac是否特殊处理了这种情况,还是语言规范实际上已经明智地处理了这种情况?

只有字面常量 'consts' 被内联,其余的每次都会被读取。 - bestsss
2个回答

1

我的JDK代码中写着out = nullInputStream(),并对该方法进行了注释:

/**
 * The following two methods exist because in, out, and err must be
 * initialized to null. The compiler, however, cannot be permitted to
 * inline access to them, since they are later set to more sensible values
 * by initializeSystemClass().
 */

initializeSystemClass() 调用 setOut0(..),它是 native 的,可能绕过了 finalSystem.setOut(..) 的工作方式相同。


有趣 - 这是OpenJDK的变体吗?我正在使用oracle/sun的实现。 - Voo
@Voo,setIn和setOut自1.1版本以来就存在于所有JDK版本中...比Java成为GPL兼容更早。 - bestsss
@bestsss 但是bozho的代码使用了out = nullInputStream(),而我的代码中写的是out = null,这两者是不同的。最终变量被覆盖的方式并不重要——我知道有几种不同的方法。看起来那段代码的作者也不知道JLS中只允许使用文字和字符串的限制 ;) - Voo
@Voo,Java7碰巧没有经验(除了G1本地泄漏使其无用)。 - bestsss
@bestsss 是的,Java7似乎已经“修复”了这个问题。 - Voo
显示剩余2条评论

1

有多种static final结构的结果。例如,字面量可能会被内联, 例如static final int xxx = 1,因为这很清楚,但例如static final int xxx = returnOne()可能不是。

原始类型和字符串只能用字面量替换,所有其他对象可能不能。最后但并非最不重要的是System.out/in违反了JLS(一旦初始化了final字段,它总是包含相同的值。)。

防止内联的几种方法:static final String xxx="xxx".intern();static final int yyy=123+return0();由于评估只能在运行时执行,因此javac将生成GETFIELD而不是BIPUSH(对于int)。


有趣 - 在JLS中找不到定义,但将其限制为文字和字符串可能会解释为什么它可以在没有这些黑客的情况下工作。是的,我知道如何避免对字符串和整数进行内联 - 我只是在代码中偶然发现它,并且很惊讶它可以在没有那些黑客的情况下工作。 - Voo
我读过的(也是唯一一本)与Java有关的书,《Java秘密》谈到了这种特殊性,因为内联任何内容的唯一方法是BIPUSH或常量表引用(我在学习“Java汇编语言”时稍后理解了这一点),这也很明显。 - bestsss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接