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

这是 JEP 解读与尝鲜系列的第 4 篇,之前的文章如下:
  • [JEP解读与尝鲜系列 1 - Java Valhalla与Java Inline class]()
  • [JEP解读与尝鲜系列 2 - JEP 142 缓存行填充简化]()
  • [JEP解读与尝鲜系列 3 - Project Loom 使用虚线程进行同步网络 IO 不阻塞的底层原理]()
在系列之前的第一篇文章 - JEP 解读与尝鲜系列 1 - Java Valhalla 与 Java Inline class 中,我介绍了 Project Valhalla 项目中的核心 Java Inline Class,总结起来其实就是 Java 中的值类型。Java 中目前只有类对象,没有值类型的对象。普通的类对象有对象头,因此这种对象可以用来做同步锁,可以使用它的 wait() notify() 等方法实现阻塞同步,同时这些对象需要在堆上面分配,通过 JVM GC 进行内存回收。并且这种对象的数组,只有数组本身是内存连续的,上面引用的对象并不是:
JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫
文章图片

Project Valhalla 提出并设计实现了 Java 的值类型,去掉了对象头,只存储它其中的值。这样减少了这种对象占用的空间,但是也让这种对象无法使用对象的 sychronization 同步,同时也失去了 对于wait() notify()这些方法的支持。同时这种对象期望是可以直接在栈上直接分配的,不用像普通对象一样需要在堆上分配,和原始类型例如 int 一样。同时这种对象的数组,期望在内存中数组的每个对象内存都是连续的:
【JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫】JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫
文章图片

这样也节省了指针的存储空间。
但是这些目前还是在设计实现中,并不是最终的实现模型,但是可以看出其中的趋势。
为了能使 Project Valhalla 最终落地实现,我们先要对 JDK 的一些元素做兼容。
JDK 中的哪些类和值类型相关 首先,最先想到的就是 Java 的原始类型对应的封装类型,例如 java.lang.Integer。原始类型是可以也是需要改造成 Java 值类型的,但是需要避免项目中使用了 Integer 对象的 wait() notify(), notifyAll() 方法,或者将这个作为 synchronization 的对象。
然后想到的就是原子类,例如 java.util.concurrent.atomic.AtomicInteger。其实在 Java 9 之后的 JMM 模型中实现了更细粒度的访问控制,例如:
private int locked = 0; private static final VarHandle LOCKED; //操作 locked 的句柄 static { try { //初始化句柄 LOCKED = MethodHandles.lookup().findVarHandle(当前类.class, "locked", int.class); } catch (Exception e) { throw new Error(e); } }//原子操作 LOCKED.compareAndSet(this, 1, 0); LOCKED.weakCompareAndSet(this, 1, 0); LOCKED.weakCompareAndSetAcquire(this, 1, 0); LOCKED.weakCompareAndSetPlain(this, 1, 0); LOCKED.weakCompareAndSetRelease(this, 1, 0);

然后还有在 Java 11 的官方文档中提出的 Value-based Classes,参考:[Java
11 Value-based Classes](https://docs.oracle.com/en/ja...),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/ja... ),将原始类型的封装类,例如 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 = https://www.it610.com/article/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 大佬一眼就看出我是误会了:
JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫
文章图片

微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:
JEP解读与尝鲜系列4 - Java 16 中对于 Project Valhalla 的铺垫
文章图片

    推荐阅读