JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫


这是 JEP 解读与尝鲜系列的第 4 篇,之前的文章如下:

  • Java
    11 Value-based Classes,Java 11 中的定义是:像是 java.util.Optionaljava.time.LocalDateTime 这种类就是 Value-based Classes,这种类的实例:

    • 本身是不可变的,虽然内部的值引用指向的是一个可变对象
    • 实现了 equalshashCodetoString 方法,并且基于它包含的值实现,而不是基于他的 identity (例如对象基址)并且也不是基于其他对象的状态。
    • 不会使用 identity-sensitive 的操作,例如通过 == 对比两个实例的相等,使用默认的基于对象基址的 hashcode 实现(例如调用 System.identityHashCode(对象)),以及作为 synchronization 的对象
    • 只通过 equals 对比对象相等,而不是 ==
    • 没有可访问的构造函数,而是通过工厂方法实例化,这些方法对返回的实例的 identity 不做任何保证,即这个返回对象的地址我们无法通过对于工厂方法的传参确定;
    • equals 相等的两个对象,需要有完全相同的行为

    这种 Value-based Classes 其实就与 Java 值类型的特征非常一致。于是,从 Java 16 开始,将 Value-based Classes 的定义进行了扩展,并且对它们的使用进行了报警限制,提示未来这些类型,不再使用普通类实现,而是使用 Project Valhalla 的 Java 值类型实现。

    JEP 390: Warnings for Value-Based Classes

    在 Java 16 中,为了给 Project Valhalla 的这一特性进行铺路,引入了一个 JEP:JEP 390: Warnings for Value-Based Classes

    在最新的 Value-based Classes 的定义中(参考:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/doc-files/ValueBased.html ),将原始类型的封装类,例如 java.lang.Integer 也纳入了这一类的定义范畴。并在此基础上,增加两个说明:

    1.非常不建议使用这一类的对象作为同步参数,例如 synchronize(obj),无法保证这个锁拥有者是谁以及是否是独占的

    这个问题倒不是因为以后要换值类型无法同步导致的,而是容易犯这种编程失误:

    Integer i = 1;
    
    for (int j = 0; j < 10; j++) {
        synchronized(i) {
            i++; //下次循环就变成另一个对象了,没有真正按照预期锁住
        }
    }
    

    2.使用 identity 相关的操作可能未来会发生变化,所以不建议使用,例如:

    • 调用 System.identityHashCode(对象) 获取基于对象在堆内存地址实现的哈希码,如果 Value-based Classes 变成值类型,值类型确定在栈上分配后,这个方法目前的机制就会有问题。
    • 调用 synchronize(obj) 同步对象,如果 Value-based Classes 变成值类型,没有普通对象的对象头,那么无法使用正常的锁膨胀同步机制,同时重量锁 mutex 由于可能值类型对象没有堆上位置也无法使用现有的机制实现。
    • 调用对象的 wait()notify()notifyAll(),由于上一条同样的影响,这些方法调用可能在未来版本带来异常。

    在 Java 16 之后,如果有这些用法,就会在编译阶段有报警提醒:

    Integer integer = 1;
    synchronized (integer) {
        
    }
    

    编译阶段会提示 Attempt to synchronize on an instance of a value-based class ,如果想关闭可以增加编译参数 -Xlint:synchronization

    如果想在运行阶段针对这种使用有提示或者错误,可以通过添加如下启动参数实现:

    • -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=1:加上这个,程序遇到这种使用,会抛出 FATAL ERROR,同时退出 JVM
    • -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2:加上这个,程序遇到这种使用,会有日志提示:
    [0.152s][info][valuebasedclasses] Synchronizing on object 0x000000069aed7788 of klass java.lang.Integer
    [0.152s][info][valuebasedclasses] 	at com.github.hashjang.shenandoah.Main.main(Main.java:8)
    [0.152s][info][valuebasedclasses] 	- locked <0x000000069aed7788> (a java.lang.Integer)
    

    同样的,由于原始类型包装类已经属于 Value-based Class,所以就不应该使用它的构造器而是使用 valueOf() 代替了,为了给大家修改的时间,目前仅仅是将构造器标记为 Deprecate for Removal

    @Deprecated(since="9", forRemoval = true)
    public Integer(int value) {
        this.value = value;
    }
    

    如果有使用会提示 'Integer(int)' is deprecated and marked for removal

    目前 JDK 中的未来可能会用值类型代替的 Value-based Classes

    目前 JDK 中的 Value-based Classes 都带有 jdk.internal.ValueBased 注解,或者他们的实现接口,父类带有这个注解,包括:

    • java.lang 包:
      • 原始类型的封装类,例如 java.lang.Integer
      • java.lang.Runtime.Version
      • 操作系统进程的句柄 java.lang.ProcessHandle 和他的实现类 java.lang.ProcessHandleImpl
    • java.time 包下的一些时间封装类
    • java.util 包:
      • Optional 相关,例如:java.util.Optional, java.util.OptionalInt, java.util.OptionalLong, java.util.OptionalDouble
      • 所有不可变集合以及底层实现的不可变元素,例如:Set.of 的返回 java.util.ImmutableCollections.AbstractImmutableSet

    一点趣事儿

    Java 16 的 Record 还让我闹了个笑话,我以为这个是 Project Valhala 的 Inline Object 已经实现了,还去 StackOverflow 问,这个 Record 为啥能有 wait() 方法,并且可以进行 synchronized 同步(因为如果是 Project Valhala 的 Inline Object 的话是没有普通类的对象头的,没法用普通类对象的方法实现同步),结果。。。。。最后还是 Goetz 大佬一眼就看出我是误会了

    image

    微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer