Unsafe 是位于 sun.misc
包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升 Java 运行效率、增强 Java 语言底层资源操作能力方面起到了很大的作用。但由于 Unsafe 类使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用 Unsafe 类会使得程序出错的概率变大,使得 Java 这种安全的语言变得不再 “安全”,因此对 Unsafe 的使用一定要慎重。
Unsafe 源码:
public final class Unsafe {
private static final Unsafe theUnsafe;
private Unsafe() {
}
@Call
erSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 仅在引导类加载器`BootstrapClassLoader`加载时才合法
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
那如若想使用这个类,该如何获取其实例?
private static Unsafe getUnsafeInstance() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
Unsafe 功能介绍
- 内存操作
通常,我们在 Java 中创建的对象都处于堆内内存(heap)中,堆内内存
是由JVM 所管控的 Java 进程内存,并且它们遵循 JVM 的内存管理机制,JVM 会采用垃圾回收机制统一管理堆内存。与之相对的是 堆外内存
,存在于 JVM 管控之外的内存区域,Java 中对堆外内存的操作,依赖于 Unsafe
提供的操作堆外内存的 native 方法。这部分主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。
使用堆外内存的原因?
-
对垃圾回收停顿的改善。由于堆外内存是直接受
操作系统管理
而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在 GC时减少回收停顿对于应用的影响。 -
提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行
内存间数据拷贝
且生命周期较短
的暂存数据,都建议存储到堆外内存。
DirectByteBuffer
是 Java 用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer 对于堆外内存的创建、使用、销毁等逻辑均由 Unsafe 提供的堆外内存API来实现。
- CAS 相关
什么是 CAS? 即比较并替换,实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS 是一条 CPU 的原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法(如compareAndSwapXXX)底层实现即为 CPU 指令 cmpxchg。
CAS 在
java.util.concurrent.atomic
相关类、Java AQS、CurrentHashMap 等实现上有非常广泛的应用。
- 线程调度
这部分,包括线程挂起、恢复、锁机制等方法。
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
- 对象操作
Unsafe 中提供 allocateInstance
方法,仅通过 Class 对象就可以创建此类的实例对象,而且 不需要 调用其构造函数、初始化代码、JVM 安全检查等。它抑制修饰符检测,也就是即使构造器是 private
修饰的也能通过此方法实例化,只需提类对象即可创建相应的对象。由于这种特性,allocateInstance
在java.lang.invoke
、Objenesis
(提供绕过类构造器的对象生成方式)、Gson
(反序列化时用到)中都有相应的应用。
val unsafeClass = Class.forName("sun.misc.Unsafe")
val theUnsafeField = unsafeClass.getDeclaredField("theUnsafe").apply {
isAccessible = true
}
val unsafe = theUnsafeField.get(null)
val allocateInstanceMethod =
unsafeClass.getMethod("allocateInstance", Class::class.java)
val student =
((allocateInstanceMethod.invoke(unsafe, Student::class.java)) as Student).apply {
name = "David"
}
- 内存屏障
在 Java 8 中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是 CPU 或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。
结语:
由于 Unsafe 中包含大量自主操作内存的方法,如若使用不当,会对程序带来许多不可控的灾难。因此对它的使用我们需要慎之又慎。
参考:
网友评论