类sun.misc.Unsafe于 2002 年推出,是 JDK 中 Java 类执行底层操作的一种方式。其大多数方法(87 种方法中的 79 种)用于访问内存,在JDK23中将通过VarHandle API(JEP 193,JDK 9)和 Foreign Function & Memory API(JEP 454,JDK 22)来彻底替换原来通过sun.misc.Unsafe实现的内存访问方法。
1.原因
基于类sun.misc.Unsafe的访问内存操作,无论是 JVM 的垃圾收集堆还是堆外内存,这些内存都不受 JVM 控制。正如该类的名称所暗示的那样,这些内存访问方法是不安全的:它们可能导致未定义的行为,包括 JVM 崩溃。因此,它们没有作为标准 API 公开。它们既不是为广泛的客户端设计的,也不是永久的。相反,它们是在假设它们专用于 JDK 的情况下引入的,并且 JDK 中的调用者在使用它们之前会执行详尽的安全检查,并且最终会将此功能的安全标准 API 添加到 Java 平台中。
然而,由于在 2002 年无法阻止sun.misc.Unsafe在 JDK 之外使用,其内存访问方法成为了库开发人员的得力工具,他们希望获得比标准 API 更强大的功能和性能。例如,sun.misc.Unsafe::compareAndSwap可以在没有java.util.concurrent.atomic API 开销的情况下对字段执行 CAS(比较和交换)操作,而sun.misc.Unsafe::setMemory可以操作堆外内存而不受 2GB 的限制。依赖于ByteBuffer操作堆外内存的java.nio.ByteBuffer库(如 Apache Hadoop 和 Cassandra),sun.misc.Unsafe::invokeCleaner通过及时释放堆外内存来提高效率。
不幸的是,并非所有库都会在调用内存访问方法之前认真执行安全检查,因此应用程序存在故障和崩溃的风险。其他方法的使用可能会导致 JVM 禁用优化,从而导致性能比使用普通 Java 数组更差。尽管如此,由于内存访问方法的使用非常广泛,因此在 JDK 9( JEP 260sun.misc.Unsafe )中没有与其他底层 API 一起封装。它在 JDK 22 中仍然开箱即用,等待安全支持的替代方案的出现。
2.过度阶段如何使用sun.misc.Unsafe类
-
–sun-misc-unsafe-memory-access=allow允许使用内存访问方法,运行时不会出现任何警告。
-
–sun-misc-unsafe-memory-access=warn允许使用内存访问方法,但第一次使用任何内存访问方法(无论是直接使用还是通过反射)时都会发出警告。也就是说,无论使用哪种内存访问方法以及使用任何特定方法多少次,最多都会发出一次警告。
-
–sun-misc-unsafe-memory-access=debug允许使用内存访问方法,但每次使用任何内存访问方法(无论是直接使用还是通过反射)都会发出一行警告和堆栈跟踪。
-
–sun-misc-unsafe-memory-access=denyUnsupportedOperationException通过在每次使用此类方法时(无论是直接使用还是通过反射)抛出一个来禁止使用内存访问方法。
3.sun.misc.Unsafe内存访问方法的替代方案
3.1.访问堆上内存(on-heap)的方法
3.1.1.原有sun.misc.Unsafe方法
3.1.1.1.变量
int INVALID_FIELD_OFFSET
int ARRAY_[TYPE]BASE_OFFSET
int ARRAY[TYPE]_INDEX_SCALE
3.1.1.2.方法
- long objectFieldOffset(Field f)
- long staticFieldOffset(Field f)
- Object staticFieldBase(Field f)
- int arrayBaseOffset(Class<?> arrayClass)
- int arrayIndexScale(Class<?> arrayClass)
这些方法用于获取偏移量或比例,然后将其与双峰方法(见下文)一起使用来读取和写入字段或数组元素。
3.1.2.替代方法
这些用例现在由MemorySegment::ofArray和VarHandle替代。在极少数情况下,这些方法会单独用于检查和操作内存中对象的物理布局。此用例没有支持的替代方案
3.2.访问堆外内存(off-heap)的方法
3.2.1.原有sun.misc.Unsafe方法
- 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)
- long getAddress(long address)
- void putAddress(long address, long x)
- int addressSize() (and int ADDRESS_SIZE)
3.2.2.现有方法
- Arena::allocate or an FFM downcall to the C library’s malloc()
- Downcall to realloc()
- Arena::close or downcall to free()
- MemorySegment::asByteBuffer
- MemorySegment::fill
- MemorySegment::copy
- MemorySegment.get(ValueLayout.Of[Type] layout, long offset)
- MemorySegment.set(ValueLayout.of[Type] layout, long offset, [type] value)
- MemorySegment.get(ValueLayout.OfAddress layout, long offset)
- MemorySegment.set(ValueLayout.ofAddress layout, long offset, MemorySegment value)
- ValueLayout.ADDRESS.byteSize()
3.3.用于访问堆上和堆外内存的方法(双峰 - 双峰方法采用一个参数,该参数要么引用堆上的对象,要么为空以表示堆外访问)。
3.3.1.原有sun.misc.Unsafe方法
- [type] get[Type](Object o, long offset)
- void put[Type](Object o, long offset, [type] x)
- [type] get[Type]Volatile(Object o, long offset)
- void put[Type]Volatile(Object o, long offset, [type] x)
- void putOrdered[Type](Object o, long offset, [type] x)
- [type] getAndAdd[Type](Object o, long offset, [type] delta)
- [type] getAndSet[Type](Object o, long offset, [type] newValue)
- boolean compareAndSwap[Type](Object o, long offset, [type] expected, [type] x)
- void setMemory(Object o, long offset, long bytes, byte value)
- void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes)
3.3.2.现有方法
- VarHandle::get
- VarHandle::set
- VarHandle::getVolatile
- VarHandle::setVolatile
- VarHandle::setRelease
- VarHandle::getAndAdd
- VarHandle::getAndSet-
- VarHandle::compareAndSet
- MemorySegment::fill or Arrays::fill
- MemorySegment::copy or System::arrayCopy
4.sun.misc.Unsafe内存访问方法的替代示例
4.1.访问堆上内存(on-heap)的方法
4.1.1.需求1
假设类Foo中有一个int字段,我们希望将其原子化为两倍。
4.1.1.1.sun.misc.Unsafe的实现源码
class Foo {
private static final Unsafe UNSAFE = ...; // A sun.misc.Unsafe object
private static final long X_OFFSET;
static {
try {
X_OFFSET = UNSAFE.objectFieldOffset(Foo.class.getDeclaredField("x"))