畅读源码系列之J.U.C(一):关于Unsafe

【引言】Unsafe这个名字首先就很诡异,字面上说它是不安全的,为什么不安全?是因为它直接操作内存了么?由于现在好多的java基础类库都有使用到Unsafe(比如J.U.C),所以第一篇我们先来说说这个包外之类。


缘起

  相信做Java开发的同学应该都清楚一个事实,就是Java是不能直接访问操作系统底层的;如果需要访问,一般是通过本地方法来访问。
  而Unsafe类的出现提供了硬件级别的原子操作,Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也不可避免的带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方是不建议使用的。
  既然很多JDK基础库都用到了这个Unsafe,虽然我们自己编码不见得回去用它,但是了解了解它的内部结构想必也没有什么坏处,接下来我们就分别看看它提供了哪些方面的独有功能。

类定义

  首先这个类在sun.misc包下,不属于Java标准;但是它用的地方还真是不少,在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
  一个很明显的特征,Unsafe是单例模式的,看源码一目了然。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package sun.misc;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;

public final class Unsafe {

private static final Unsafe theUnsafe;
......

private Unsafe() {
}

@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}

内存管理

  Unsafe提供了一系列对内存操作的方法(比如分配、释放内存等);和C语言有类似的功效了。

  • allocateMemory(分配内存)
  • reallocateMemory(重新分配内存)
  • freeMemory(释放内存 )
  • copyMemory(拷贝内存)
  • getAddress(获取内存地址)
  • getInt(获取内存地址指向的整数)
  • getIntVolatile(获取内存地址指向的整数,并支持volatile语义)
  • putInt(将整数写入指定内存地址)
  • putIntVolatile(将整数写入指定内存地址,并支持volatile语义)
  • putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)
  • ……

非常规的对象实例化

  allocateInstance()方法,提供了不同于new的另一种对象创建方式,使用allocateInstance()方法可以直接生成对象实例,并且也无需调用构造方法和其它初始化方法。(这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法 – 说实在的这个特性不是太明白)

操作类、对象、变量

  Unsafe提供了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法,通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

数组操作

  Unsafe提供了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。

多线程同步

  Unsafe提供了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法;有些方法已经deprecated,不建议使用了,需要注意。
  Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。

挂起与恢复

  Unsafe提供了park、unpark等方法(实际就是AQS里面使用到了的park和unpark),将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

内存屏障

  Unsafe在Java 8新引入loadFence、storeFence、fullFence等方法,用于定义内存屏障,避免代码重排序。
  loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。

------2019 Lin.C ------