《HarmonyOS:领航者说》技术公开课来啦,大咖分享、实战解码,不容错过 了解详情
写点什么

JEP 456:准备删除 Unsafe 中的内存访问方法

  • 2024-07-10
    北京
  • 本文字数:2964 字

    阅读完需:约 10 分钟

JEP 456:准备删除 Unsafe 中的内存访问方法

JEP 471(弃用 sun.misc.Unsafe 中的内存访问方法以备删除)已经在 JDK 23 中发布。该 JEP 建议弃用 Unsafe 类中的内存访问方法,以便在将来的版本中删除。这些不再支持的方法已经被标准 API 所取代:JEP 193(变量句柄,已在 JDK 9 中交付)和 JEP 454(外部函数和内存 API,已在 JDK 22 中交付)。


弃用这些方法的主要目的是为最终删除sun.misc.Unsafe中的内存访问方法做准备。编译时和运行时警告会突出显示这些方法的使用情况,开发人员可以借此识别并迁移到受支持的替代方法。这一转变的目标是确保应用程序能够顺利过渡到现代 JDK 版本,从而增强安全性和性能。


现在,有两个标准 API 为sun.misc.Unsafe提供了安全高效的替代方案。VarHandle API(即在 JDK 9 中交付的 JEP 193)提供了安全操作堆内存的方法,可以确保操作有效执行并且不会出现未定义的行为。外部函数和内存 API(即在 JDK 22 中交付的 JEP 454)提供了安全的堆外内存访问方法,通常与 VarHandle 搭配使用来管理 JVM 堆内和堆外内存。这些 API 承诺:不会出现未定义的行为、长期稳定以及更好地与 Java 工具和文档集成。


已弃用的sun.misc.Unsafe方法分为三类:堆内、堆外和双模(可以访问堆内和堆外内存的方法)。堆内方法包括:


long objectFieldOffset(Field f)long staticFieldOffset(Field f)Object staticFieldBase(Field f)int arrayBaseOffset(Class<?> arrayClass)int arrayIndexScale(Class<?> arrayClass)
复制代码


这些方法可以用VarHandleMemorySegment::ofArray及其重载方法代替。例如,考虑下面的例子:


class Foo {
private static final Unsafe UNSAFE = ...; // 一个 sun.misc.Unsafe 对象
private static final long X_OFFSET;
static { try { X_OFFSET = UNSAFE.objectFieldOffset(Foo.class.getDeclaredField("x")); } catch (Exception ex) { throw new AssertionError(ex); } }
private int x;
public boolean tryToDoubleAtomically() { int oldValue = x; return UNSAFE.compareAndSwapInt(this, X_OFFSET, oldValue, oldValue * 2); }}
复制代码


上述代码可以用 VarHandle 实现如下:


class Foo {
private static final VarHandle X_VH;
static { try { X_VH = MethodHandles.lookup().findVarHandle(Foo.class, "x", int.class); } catch (Exception ex) { throw new AssertionError(ex); } }
private int x;
public boolean tryAtomicallyDoubleX() { int oldValue = x; return X_VH.compareAndSet(this, oldValue, oldValue * 2); }}
复制代码


堆外方法主要有以下这些:


long allocateMemory(long bytes)long reallocateMemory(long address, long bytes)void freeMemory(long address)void invokeCleaner(java.nio.ByteBuffer directBuffer)void setMemory(long address, long bytes, byte value)void copyMemory(long srcAddress, long destAddress, long bytes)[type] get[Type](long address)void put[Type](long address, [type] x)
复制代码


这些方法可以用MemorySegment 操作替换。考虑下面的例子:


class OffHeapIntBuffer {
private static final Unsafe UNSAFE = ...;
private static final int ARRAY_BASE = UNSAFE.arrayBaseOffset(int[].class); private static final int ARRAY_SCALE = UNSAFE.arrayIndexScale(int[].class);
private final long size; private long bufferPtr;
public OffHeapIntBuffer(long size) { this.size = size; this.bufferPtr = UNSAFE.allocateMemory(size * ARRAY_SCALE); }
public void deallocate() { if (bufferPtr == 0) return; UNSAFE.freeMemory(bufferPtr); bufferPtr = 0; }
private boolean checkBounds(long index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(index); return true; }
public void setVolatile(long index, int value) { checkBounds(index); UNSAFE.putIntVolatile(null, bufferPtr + ARRAY_SCALE * index, value); }
public void initialize(long start, long n) { checkBounds(start); checkBounds(start + n-1); UNSAFE.setMemory(bufferPtr + start * ARRAY_SCALE, n * ARRAY_SCALE, 0); }
public int[] copyToNewArray(long start, int n) { checkBounds(start); checkBounds(start + n-1); int[] a = new int[n]; UNSAFE.copyMemory(null, bufferPtr + start * ARRAY_SCALE, a, ARRAY_BASE, n * ARRAY_SCALE); return a; }
}
复制代码


上述代码使用标准 API ArenaMemorySegment 替换后如下:


class OffHeapIntBuffer {
private static final VarHandle ELEM_VH = ValueLayout.JAVA_INT.arrayElementVarHandle();
private final Arena arena; private final MemorySegment buffer;
public OffHeapIntBuffer(long size) { this.arena = Arena.ofShared(); this.buffer = arena.allocate(ValueLayout.JAVA_INT, size); }
public void deallocate() { arena.close(); }
public void setVolatile(long index, int value) { ELEM_VH.setVolatile(buffer, 0L, index, value); }
public void initialize(long start, long n) { buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start, ValueLayout.JAVA_INT.byteSize() * n) .fill((byte) 0); }
public int[] copyToNewArray(long start, int n) { return buffer.asSlice(ValueLayout.JAVA_INT.byteSize() * start, ValueLayout.JAVA_INT.byteSize() * n) .toArray(ValueLayout.JAVA_INT); }
}
复制代码


迁移将分几个阶段进行,每个阶段对应一个单独的 JDK 版本。在第一阶段(从 JDK 23 开始),所有内存访问方法都将被弃用,并且将发出编译时警告。第二阶段(计划从 JDK 25 或更早的版本开始)将在发现使用已弃用方法的情况时发出运行时警告。第三阶段(计划从 JDK 26 或更高的版本开始)将进一步升级响应,在发现对这些方法的调用时默认抛出异常。最后,第四和第五阶段将删除已弃用的方法。这两个阶段可能发生在同一版本中。开发人员可以使用新增的命令行选项--sun-misc-unsafe-memory-access={allow|warn|debug|deny}来管理弃用警告并评估对其应用程序的影响。


弃用sun.misc.Unsafe内存访问方法是增强 Java 平台完整性和安全性的一个重要步骤。借助 VarHandle 及外部函数和内存 API,开发人员可以保证其应用程序的健壮性,并兼容未来的 JDK 版本。这种分阶段的方法为迁移提供了充足的时间,既有助于 Java 开发最佳实践的推广,又能够尽可能地减少由此带来的影响。


原文链接:

https://www.infoq.com/news/2024/06/jep-456-removing-unsafe-methods/

2024-07-10 08:004279

评论

发布
暂无评论
发现更多内容

如何优雅的应对线上故障?

老张

监控告警 线上故障 应急响应 问题复盘

观测云全面兼容 Prometheus 生态

观测云

加密项目的生死轮回:11亿美元融资的35个“死亡”VC项目剖析

区块链软件开发推广运营

dapp开发 区块链开发 链游开发 NFT开发

B站安全开发流程落地实践

我再BUG界嘎嘎乱杀

网络安全 安全 哔哩哔哩 网安 安全开发流程

低代码实践:题型物料化设计(二)

XIAOJUSURVEY

开源 低代码 组件化 问卷模型

2024 年五大最佳构建内部工具的开源项目

NocoBase

低代码 开源项目 无代码平台

无缝融入,即刻智能[1]:MaxKB知识库问答系统,零编码嵌入第三方业务系统,定制专属智能方案,用户满意度飙升

汀丶人工智能

agent 智能问答 rag

Keepalived基本原理

天翼云开发者社区

Linux keepalive

RocksDB 二级缓存

天翼云开发者社区

RocksDB

Zilliz向你发来邀请:请加入Stack Overflow吧!入股不亏!

Zilliz

人工智能 Zilliz 向量数据库 大语言模型

Google 推出 Gemma 2 2B 端侧模型;Github 新服务帮助开发者选择 AI 模型 丨 RTE 开发者日报

声网

华为云DTSE助力易印云化架构升级,推动印刷包装行业智能化进程

华为云开发者联盟

人工智能 物联网 华为云 waf 企业号 8 月 PK 榜

最全数据资产目录模板汇总,有必要了解一下!(附下载)

极盾科技

数据安全

七牛云点播实时转码上线,边转边播,又快又省

七牛云

转码

低代码实践:题型物料场景化设计

XIAOJUSURVEY

开源 低代码 搭建平台 场景化思维 组件化架构

低代码实践:题型物料化设计(一)

XIAOJUSURVEY

开源 低代码 组件化 问卷

揭秘抖音视频列表数据接口:获取数据的新利器

tbapi

抖音API 抖音视频数据接口 抖音视频列表接口 抖音视频列表数据采集 抖音视频API接口

华为大咖说 | 数字化解码:如何用日常语言讲明白数字世界?

云计算 华为云

ZOS对象存储跨域资源访问的实现和使用

天翼云开发者社区

对象存储 跨域

JEP 456:准备删除 Unsafe 中的内存访问方法_编程语言_A N M Bazlur Rahman_InfoQ精选文章