这是 JEP 解读与尝鲜系列的第 4 篇,之前的文章如下:在系列之前的第一篇文章 - JEP 解读与尝鲜系列 1 - Java Valhalla 与 Java Inline class 中,我介绍了 Project Valhalla 项目中的核心 Java Inline Class,总结起来其实就是 Java 中的值类型。Java 中目前只有类对象,没有值类型的对象。普通的类对象有对象头,因此这种对象可以用来做同步锁,可以使用它的
- [JEP解读与尝鲜系列 1 - Java Valhalla与Java Inline class]()
- [JEP解读与尝鲜系列 2 - JEP 142 缓存行填充简化]()
- [JEP解读与尝鲜系列 3 - Project Loom 使用虚线程进行同步网络 IO 不阻塞的底层原理]()
wait()
notify()
等方法实现阻塞同步,同时这些对象需要在堆上面分配,通过 JVM GC 进行内存回收。并且这种对象的数组,只有数组本身是内存连续的,上面引用的对象并不是:文章图片
Project Valhalla 提出并设计实现了 Java 的值类型,去掉了对象头,只存储它其中的值。这样减少了这种对象占用的空间,但是也让这种对象无法使用对象的 sychronization 同步,同时也失去了 对于
wait()
notify()
这些方法的支持。同时这种对象期望是可以直接在栈上直接分配的,不用像普通对象一样需要在堆上分配,和原始类型例如 int 一样。同时这种对象的数组,期望在内存中数组的每个对象内存都是连续的:【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.Optional
和 java.time.LocalDateTime
这种类就是 Value-based Classes,这种类的实例:- 本身是不可变的,虽然内部的值引用指向的是一个可变对象
- 实现了
equals
,hashCode
和toString
方法,并且基于它包含的值实现,而不是基于他的 identity (例如对象基址)并且也不是基于其他对象的状态。 - 不会使用 identity-sensitive 的操作,例如通过
==
对比两个实例的相等,使用默认的基于对象基址的 hashcode 实现(例如调用System.identityHashCode(对象)
),以及作为 synchronization 的对象 - 只通过
equals
对比对象相等,而不是==
- 没有可访问的构造函数,而是通过工厂方法实例化,这些方法对返回的实例的 identity 不做任何保证,即这个返回对象的地址我们无法通过对于工厂方法的传参确定;
equals
相等的两个对象,需要有完全相同的行为
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()
,由于上一条同样的影响,这些方法调用可能在未来版本带来异常。
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
- Optional 相关,例如:
文章图片
微信搜索“我的编程喵”关注公众号,每日一刷,轻松提升技术,斩获各种offer:
文章图片
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)