前言
几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了 sun.misc.Unsafe,比如 Netty、Cassandra、Hadoop、Kafka 等。
Unsafe 类在提升 Java 运行效率,增强 Java 语言底层操作能力方面起了很大的作用。但 Unsafe 类在 sun.misc 包下,不属于 Java 标准。
很早之前,在阅读并发编程相关类的源码时,看到 Unsafe 类,产生了一个疑惑:既然是并发编程中用到的类,为什么命名为 Unsafe 呢?
深入了解之后才知道,这里的 Unsafe 并不是说线程安全与否,而是指:该类对于普通的程序员来说是”危险“的,一般应用开发者不会也不应该用到此类。
因为 Unsafe 类功能过于强大,提供了一些可以绕开 JVM 的更底层功能。它让 Java 拥有了像 C 语言的指针一样操作内存空间的能力,能够提升效率,但也带来了指针的问题。官方并不建议使用,也没提供文档支持,甚至计划在高版本中去掉该类。
但对于开发者来说,了解该类提供的功能更有助于我们学习 CAS、并发编程等相关的知识,还是非常有必要学习和了解的。
Unsafe 的构造
Unsafe 类是"final"的,不允许继承,且构造函数是 private,使用了单例模式来通过一个静态方法 getUnsafe() 来获取。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"> private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
</pre>
在 getUnsafe 方法中对单例模式中的对象创建做了限制,如果是普通的调用会抛出一个 SecurityException 异常。只有由主类加载器加载的类才能调用这个方法。
那么,如何获得 Unsafe 类的对象呢?通常采用反射机制:
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public static Unsafe getUnsafe() throws IllegalAccessException {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
</pre>
当获得 Unsafe 对象之后,就可以“为所欲为”了。下面就来看看,通过 Unsafe 方法,我们可以做些什么。
Unsafe 的主要功能
可先从根据下图从整体上了解一下 Unsafe 提供的功能:
[图片上传失败...(image-f5548b-1652079770206)]
Unsafe 功能概述
下面挑选重要的功能进行讲解。
| 内存管理
Unsafe 的内存管理功能主要包括:普通读写、volatile 读写、有序写入、直接操作内存等分配内存与释放内存的功能。
①普通读写
Unsafe 可以读写一个类的属性,即便这个属性是私有的,也可以对这个属性进行读写。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 获取内存地址指向的整数
public native int getInt(Object var1, long var2);
// 将整数写入指定内存地址
public native void putInt(Object var1, long var2, int var4);
</pre>
getInt 用于从对象的指定偏移地址处读取一个 int。putInt 用于在对象指定偏移地址处写入一个 int。其他原始类型也提供有对应的方法。
另外,Unsafe 的 getByte、putByte 方法提供了直接在一个地址上进行读写的功能。
②volatile 读写
普通的读写无法保证可见性和有序性,而 volatile 读写就可以保证可见性和有序性。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 获取内存地址指向的整数,并支持volatile语义
public native int getIntVolatile(Object var1, long var2);
// 将整数写入指定内存地址,并支持volatile语义
public native void putIntVolatile(Object var1, long var2, int var4);
</pre>
volatile 读写要保证可见性和有序性,相对普通读写更加昂贵。
③有序写入
有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 将整数写入指定内存地址、有序或者有延迟的方法
public native void putOrderedInt(Object var1, long var2, int var4);
</pre>
而与 volatile 写入相比 putOrderedXX 写入代价相对较低,putOrderedXX 写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。
④直接操作内存
Unsafe 提供了直接操作内存的能力:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 分配内存
public native long allocateMemory(long var1);
// 重新分配内存
public native long reallocateMemory(long var1, long var3);
// 内存初始化
public native void setMemory(long var1, long var3, byte var5);
// 内存复制
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 清除内存
public native void freeMemory(long var1);
</pre>
对应操作内存,也提供了一些获取内存信息的方法:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 获取内存地址
public native long getAddress(long var1);
public native int addressSize();
public native int pageSize();
</pre>
值得注意的是:利用 copyMemory 方法可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现 clone 方法,但只能做到对象浅拷贝。
| 非常规对象实例化
通常,我们通过 new 或反射来实例化对象,而 Unsafe 类提供的 allocateInstance 方法,可以直接生成对象实例,且无需调用构造方法和其他初始化方法。
这在对象反序列化的时候会很有用,能够重建和设置 final 字段,而不需要调用构造方法。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 直接生成对象实例,不会调用这个实例的构造方法
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
</pre>
| 类加载
通过以下方法,可以实现类的定义、创建等操作。
<pre class="prettyprint hljs gradle" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 方法定义一个类,用于动态地创建类
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
// 动态的创建一个匿名内部类
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
// 判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class<?> var1);
// 保证已经初始化过一个类
public native void ensureClassInitialized(Class<?> var1);
</pre>
| 偏移量相关
Unsafe 提供以下方法获取对象的指针,通过对指针进行偏移,不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到 JVM 已经认定为垃圾、可以进行回收的对象。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量
public native long staticFieldOffset(Field var1);
// 获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量
public native long objectFieldOffset(Field var1);
// 返回Field所在的对象
public native Object staticFieldBase(Field var1);
// 返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量
public native int arrayBaseOffset(Class<?> var1);
// 计算数组中第一个元素所占用的内存空间
public native int arrayIndexScale(Class<?> var1);
</pre>
| 数组操作
数组操作提供了以下方法:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> var1);
// 获取数组中元素的增量地址
public native int arrayIndexScale(Class<?> var1);
</pre>
arrayBaseOffset 与 arrayIndexScale 配合起来使用,就可以定位数组中每个元素在内存中的位置。
由于 Java 的数组最大值为 Integer.MAX_VALUE,使用 Unsafe 类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是 C 数组,因此需要注意在合适的时间释放内存。
| 线程调度
线程调度相关方法如下:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 唤醒线程
public native void unpark(Object var1);
// 挂起线程
public native void park(boolean var1, long var2);
// 用于加锁,已废弃
public native void monitorEnter(Object var1);
// 用于加锁,已废弃
public native void monitorExit(Object var1);
// 用于加锁,已废弃
public native boolean tryMonitorEnter(Object var1);
</pre>
通过 park 方法将线程进行挂起, 线程将一直阻塞到超时或中断条件出现。unpark 方法可以终止一个挂起的线程,使其恢复正常。
整个并发框架中对线程的挂起操作被封装在 LockSupport 类中,LockSupport 类中有各种版本 pack 方法,但最终都调用了 Unsafe.park() 方法。
| CAS 操作
Unsafe 类的 CAS 操作可能是使用最多的方法。它为 Java 的锁机制提供了一种新的解决办法,比如 AtomicInteger 等类都是通过该方法来实现的。compareAndSwap 方法是原子的,可以避免繁重的锁机制,提高代码效率。
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
</pre>
CAS 一般用于乐观锁,它在 Java 中有广泛的应用,ConcurrentHashMap,ConcurrentLinkedQueue 中都有用到 CAS 来实现乐观锁。
| 内存屏障
JDK8 新引入了用于定义内存屏障、避免代码重排的方法:
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">// 保证在这个屏障之前的所有读操作都已经完成
public native void loadFence();
// 保证在这个屏障之前的所有写操作都已经完成
public native void storeFence();
// 保证在这个屏障之前的所有读写操作都已经完成
public native void fullFence();
</pre>
| 其他
当然,Unsafe 类中还提供了大量其他的方法,比如上面提到的 CAS 操作,以 AtomicInteger 为例,当我们调用 getAndIncrement、getAndDecrement 等方法时,本质上调用的就是 Unsafe 的 getAndAddInt 方法。
<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
</pre>
在实践的过程中,如果阅读其他框架或类库实现,当发现用到 Unsafe 类,可对照该类的整体功能,结合应用场景进行分析,即可大概了解其功能。
小结
经过本文的分析,想必大家在阅读源码时,再遇到 Unsafe 类的调用,一定大概猜出它是用来干什么的。
使用 Unsafe 类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解,且有必要的情况下才建议使用。
网友评论