gpt4 book ai didi

java - 在 Kotlin CI 测试期间静态最终变量初始化(在 Java 中)不正确

转载 作者:行者123 更新时间:2023-12-04 04:31:29 24 4
gpt4 key购买 nike

我管理一个开源项目,有一个用户报告了一种情况,根据 Java 的类中静态变量的初始化顺序,我认为这是不可能的。 static final 的值class 变量不正确,显然是由于依赖项的静态方法基于其自己的静态最终变量的不同结果造成的。
我想了解发生了什么,以便找出最佳解决方法。此刻,我很困惑。
问题
我的项目的主要入口点是类 SystemInfo 它具有以下构造函数:

public SystemInfo() {
if (getCurrentPlatform().equals(PlatformEnum.UNKNOWN)) {
throw new UnsupportedOperationException(NOT_SUPPORTED + Platform.getOSType());
}
}
单独运行时,问题不会重现;但是当作为正在执行的许多测试的一部分运行时,一个更大的构建( mvn install)始终是可重现的, 暗示问题可能与多线程或多个 fork 有关 . (澄清:我的意思是同时初始化两个不同类中的静态成员,以及与此过程相关的各种 JVM 内部锁定/同步机制。)
他们收到以下结果:

java.lang.UnsupportedOperationException: Operating system not supported: JNA Platform type 2


这个异常(exception)意味着当 SystemInfo 时有两件事是正确的实例化开始:
  • getCurrentPlatform()的结果是枚举值 PlatformEnum.UNKNOWN
  • Platform.getOSType()的结果是 2

  • 不过,这种情况应该是不可能的;值 2 将返回 WINDOWS,而 unknown 将返回 2 以外的值。因为这两个变量都是 staticfinal他们永远不应该同时达到这种状态。
    (用户的)MCRE
    我试图自己重现这个并失败了,我依赖于用户在他们的基于 Kotlin(kotest)框架中执行测试的报告。
    用户的 MCRE 只需调用此构造函数作为在 Windows 操作系统上运行的大量测试的一部分:
    public class StorageOnSystemJava {
    public StorageOnSystemJava(SystemInfo info) {
    }
    }

    class StorageOnSystemJavaTest {
    @Test
    void run() {
    new StorageOnSystemJava(new SystemInfo());
    }
    }
    底层代码 getCurrentPlatform()方法简单地返回此 static final 的值多变的。
    public static PlatformEnum getCurrentPlatform() {
    return currentPlatform;
    }
    这是一个 static final变量填充为类中的第一行(因此它应该是初始化的第一行):
    private static final PlatformEnum currentPlatform = queryCurrentPlatform();
    在哪里
    private static PlatformEnum queryCurrentPlatform() {
    if (Platform.isWindows()) {
    return WINDOWS;
    } else if (Platform.isLinux()) {
    // other Platform.is*() checks here
    } else {
    return UNKNOWN; // The exception message shows the code reaches this point
    }
    }
    这意味着在类初始化期间,所有 Platform.is*()检查返回错误。
    然而,如上所述,这不应该发生。这些是调用 JNA 的 Platform 类静态方法。第一次检查,应该返回 true (并且,如果在构造函数或实例化后的任何代码中调用)是:
    public static final boolean isWindows() {
    return osType == WINDOWS || osType == WINDOWSCE;
    }
    哪里 osTypestatic final变量定义如下:
    public static final int WINDOWS = 2;

    private static final int osType;

    static {
    String osName = System.getProperty("os.name");
    if (osName.startsWith("Linux")) {
    // other code
    }
    else if (osName.startsWith("Windows")) {
    osType = WINDOWS; // This is the value being assigned, showing the "2" in the exception
    }
    // other code
    }
    根据我对初始化顺序的理解, Platform.isWindows()应该总是返回 true (在 Windows 操作系统上)。我不明白它怎么可能返回 false从我自己的代码的静态变量初始化中调用时。我已经尝试了静态方法和紧跟在变量声明之后的静态初始化块。
    预期的初始化顺序
  • 用户拨打 SystemInfo构造函数
  • SystemInfo类初始化开始(“T 是一个类并且创建了 T 的一个实例。”)
  • static final currentPlatform初始化程序遇到变量(类的第一行)
  • 初始化程序调用静态方法 queryCurrentPlatform()获得结果(如果在静态变量声明之后立即在静态块中分配值,则结果相同)
  • Platform.isWindows()静态方法被调用
  • Platform类已初始化(“T 是一个类并且调用了 T 的静态方法。”)
  • Platform类集osType将值设置为 2 作为初始化的一部分
  • Platform初始化完成,静态方法isWindows()返回 true
  • queryCurrentPlatform()看到 true结果并设置 currentPlatform变量值 ( 这没有按预期发生! )
  • SystemInfo类初始化完成,其构造函数执行,显示冲突值并抛出异常。

  • 解决方法
    一些解决方法可以解决问题,但我不明白他们为什么这样做:
  • 执行 Platform.isWindows()在实例化过程中随时检查(包括构造函数)是否正确返回 true并适本地分配枚举。
  • 这包括 currentPlatform 的惰性实例化变量(删除 final 关键字),或忽略枚举并直接调用 JNA 的 Platform类(class)。

  • 将第一个调用移至 static方法 getCurrentPlatform()出构造函数。

  • 这些解决方法意味着 一个可能的根本原因与执行 static 有关类初始化期间多个类的方法 .具体来说:
  • 在初始化期间,Platform.isWindows()检查显然返回 false因为代码到达 else区块
  • 初始化后(在实例化期间),Platform.isWindows()支票返回 true . (因为它基于 static final 值,所以它不应该返回不同的结果。)

  • 研究
    我已经彻底审查了多个关于 Java 的教程,清楚地显示了初始化顺序,以及这些其他 SO 问题和链接的 Java 语言规范:
  • Java static class initialization
  • in what order are static blocks and static variables in a class executed?
  • In what order are the different parts of a class initialized when a class is loaded in the JVM?
  • 最佳答案

    它不是多线程,因为 JVM 会在类初始化时阻止其他线程访问该类。 Java 语言规范要求此行为,section 12.4.2 , 第2步:

    If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this step.


    JVM 极不可能在这方面存在错误,因为它会导致重复执行初始化程序,这将非常明显。
    但是,如果出现以下情况,静态 final 字段可能看起来具有变化的值:
  • 有一个初始值设定项之间的循环依赖
    同一部分,第 3 步写道:

    If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.


    因此,递归初始化可能允许线程在分配之前读取静态最终字段。只有当类初始值设定项在初始值设定项之间创建循环依赖时才会发生这种情况。
  • 有人 (ab) 使用 反射 重新分配静态最终字段
  • 该类由 加载不止一个类加载器
    在这种情况下,每个类都有自己的静态字段副本,并且可能以不同的方式对其进行初始化。
  • 如果该字段是 编译时常量表达式 ,代码为 不同时间编译
    规范要求编译时常量表达式由编译器内联。如果不同的类在不同的时间编译,被内联的值可能不同。 (在您的情况下,表达式不是编译时间常数;我只是为了将来的访问者才提到这种可能性)。

  • 根据您提供的证据,无法确定其中哪些适用。这就是为什么我建议进一步调查。

    关于java - 在 Kotlin CI 测试期间静态最终变量初始化(在 Java 中)不正确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68415179/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com