关于java:是否可以禁用静态最终变量的javac内联?

is it possible to disable javac's inlining of static final variables?

Java静态编译器(javac)内联一些静态最终变量,并将值直接带到常量池中。考虑以下示例。 A类定义了一些常量(公共静态最终变量):

1
2
3
4
public class A {
    public static final int INT_VALUE = 1000;
    public static final String STRING_VALUE ="foo";
}

B类使用以下常量:

1
2
3
4
5
6
7
8
public class B {
    public static void main(String[] args) {
        int i = A.INT_VALUE;
        System.out.println(i);
        String s = A.STRING_VALUE;
        System.out.println(s);
    }
}

当您编译类B时,javac从类A获取这些常量的值,并在B.class中内联这些值。结果,在编译时必须将类A的依赖项B从字节码中删除。这是一个非常特殊的行为,因为您在编译时正在烘烤这些常量的值。而且您会认为这是JIT编译器在运行时可以做的最简单的事情之一。

有什么方法或任何隐藏的编译器选项可让您禁用Javac的这种内联行为吗?对于背景,我们正在考虑进行字节码分析以实现依赖关系,这是字节码分析无法检测到编译时依赖关系的少数情况之一。谢谢!

编辑:这是一个令人烦恼的问题,因为通常我们不控制所有源代码(例如,定义常量的第三方库)。我们有兴趣从使用常量的角度检测这些依赖关系。由于从使用常量的代码中删除了引用,因此没有简单的方法可以检测到它们,而无需进行源代码分析。


Java Puzzlers(Joshua Bloch)的第93项说,您可以通过防止将最终值视为常量来解决此问题。例如:

1
2
3
4
public class A {
  public static final int INT_VALUE = Integer.valueOf(1000).intValue();
  public static final String STRING_VALUE ="foo".toString();
}

当然,如果您无权访问定义常量的代码,则这些都不相关。


我不相信最简单的解决方法是将它们公开为属性而不是字段:

1
2
3
4
5
6
7
8
9
10
11
public class A {
    private static final int INT_VALUE = 1000;
    private static final String STRING_VALUE ="foo";

    public static int getIntValue() {
        return INT_VALUE;
    }
    public static String getStringValue() {
        return STRING_VALUE;
    }
}

不要忘记,在某些情况下,内联对于使用该值必不可少-例如,如果要在开关块中使用INT_VALUE作为案例,则必须将其指定为常量值。


要停止内联,您需要使值成为非编译时间常数(JLS术语)。您可以在不使用函数的情况下执行此操作,而在初始化程序表达式中使用null可以创建最少的字节码。

1
public static final int INT_VALUE = null!=null?0: 1000;

尽管javac在其代码生成中非常真实,但应对其进行优化,以使其为紧跟在整数中的立即数,然后在静态初始化程序中将其存储到静态字段。


JLS 13.4.9处理此问题。他们的建议是,如果该值可能以任何方式改变,则基本上避免编译时常量。

(One reason for requiring inlining of
constants is that switch statements
require constants on each case, and no
two such constant values may be the
same. The compiler checks for
duplicate constant values in a switch
statement at compile time; the class
file format does not do symbolic
linkage of case values.)

The best way to avoid problems with
"inconstant constants" in
widely-distributed code is to declare
as compile time constants only values
which truly are unlikely ever to
change. Other than for true
mathematical constants, we recommend
that source code make very sparing use
of class variables that are declared
static and final. If the read-only
nature of final is required, a better
choice is to declare a private static
variable and a suitable accessor
method to get its value. Thus we
recommend:

1
2
private static int N;
public static int getN() { return N; }

rather than:

1
public static final int N = ...;

There is no problem with:

1
public static int N = ...;

if N need not be read-only.


像这样重写A类:

1
2
3
4
5
6
7
8
9
public class A {
    public static final int INT_VALUE;
    public static final String STRING_VALUE;

    static {
        INT_VALUE = 1000;
        STRING_VALUE ="foo";
    }
}

我认为这是一个严重的错误。 Java不是C / C ++。有一个原则(或没有)"编译一次,到处运行"。

在这种情况下,当更改A类时。任何引用A.CONST_VALUE的类都必须重新编译,并且它们几乎不知道是否更改了A类。


jmake是一个开源项目,声称可以完成跟踪Java文件之间的依赖关系并逐步编译所需的最少文件集的全部工作。它声称可以正确处理对静态最终常量的更改,尽管有时需要重新编译整个项目。它甚至比类文件更精细地处理更改。如果(例如)方法C.m()的签名发生更改,则它仅重新编译实际上依赖于m()的类,而不是所有使用C的类。

免责声明:我没有使用jmake的经验。


我最近遇到了一个类似的问题,如上所述,可以使用非编译时表达式来解决这种内联问题,例如:

1
2
3
4
5
6
public final class A {

    public static final int INT_VALUE = constOf(1000);
    public static final String STRING_VALUE = constOf("foo");

}

constOf方法族仅仅是:

1
2
3
4
5
6
7
8
9
10
11
// @formatter:off
public static boolean constOf(final boolean value) { return value; }
public static byte constOf(final byte value) { return value; }
public static short constOf(final short value) { return value; }
public static int constOf(final int value) { return value; }
public static long constOf(final long value) { return value; }
public static float constOf(final float value) { return value; }
public static double constOf(final double value) { return value; }
public static char constOf(final char value) { return value; }
public static < T > T constOf(final T value) { return value; }
// @formatter:on

这比Integer.valueOf(1000).intValue()null!=null?0: 1000等其他建议短


我觉得Java紧密依赖动态编译,它不像C ++那样执行任何复杂的编译逻辑。

您可以使用JIT编译器尝试一些选项,这些选项可以进行运行时优化,其中某些选项可以禁用/启用此选项。

在默认的javac中,您可能无法获得该选项。你必须使用
1.某种类型的依赖图,例如扩展或实现
2.使用基于方法的链接。

-s