Unsafe类详解

Unsafe

第一节 简介

Java通常情况下无法直接访问到底层的操作系统,只能通过 native 方法,但JVM通过 Unsafe 类开了一个后门提供硬件级别的原子操作,因为其特殊的用途所以其使用是受限制的,一般情况下也几乎不会使用到这个后门。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class Unsafe {

static {
Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}

private Unsafe() {}

private static final Unsafe theUnsafe = new Unsafe();
private static final jdk.internal.misc.Unsafe theInternalUnsafe = jdk.internal.misc.Unsafe.getUnsafe();

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

...
}

Unsafe类采用单例模式,通过静态方法 getUnsafe() 来创建,且必须要通过主类加载器加载的类才可以调用,否则抛出 SecurityException 异常。

可以通过反射机制来使用:

1
2
3
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(Unsafe.class);

第二节 方法分类

一. 内存管理

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
   @ForceInline
public Object getObject(Object o, long offset) {
return theInternalUnsafe.getObject(o, offset);
}
@HotSpotIntrinsicCandidate
public native Object getObject(Object o, long offset);

@ForceInline
public void putObject(Object o, long offset, Object x) {
theInternalUnsafe.putObject(o, offset, x);
}
@HotSpotIntrinsicCandidate
public native void putObject(Object o, long offset, Object x);

//从给定的Java变量中获取值,获取内存地址指向的整数
@ForceInline
public int getInt(Object o, long offset) {
return theInternalUnsafe.getInt(o, offset);
}

//双寄存器寻址模式,当对象引用为空,使用偏移量作为绝对地址,要区别于单寄存器寻址模式
@HotSpotIntrinsicCandidate
public native int getInt(Object o, long offset);

//获取内存地址指向的整数,并支持volatile语义
@HotSpotIntrinsicCandidate
public native int getIntVolatile(Object o, long offset);

//将整数写入指定内存地址
@ForceInline
public void putInt(Object o, long offset, int x) {
theInternalUnsafe.putInt(o, offset, x);
}
@HotSpotIntrinsicCandidate
public native void putInt(Object o, long offset, int x);
//将整数写入指定内存地址,并支持volatile语义
@HotSpotIntrinsicCandidate
public native void putIntVolatile(Object o, long offset, int x);

//将整数写入指定内存地址、有序或者有延迟的方法
/** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */
@ForceInline
public void putOrderedInt(Object o, long offset, int x) {
theInternalUnsafe.putIntRelease(o, offset, x);
}
@HotSpotIntrinsicCandidate
public final void putIntRelease(Object o, long offset, int x) {
putIntVolatile(o, offset, x);
}
@HotSpotIntrinsicCandidate
public native void putIntVolatile(Object o, long offset, int x);

...

//获取内存地址
@ForceInline
public long getAddress(long address) {
return theInternalUnsafe.getAddress(address);
}

//分配内存。以字节为单位的新的native内存块,其内容未初始化,通常是垃圾。
public long allocateMemory(long bytes) {
allocateMemoryChecks(bytes);

if (bytes == 0) {
return 0;
}

long p = allocateMemory0(bytes);
if (p == 0) {
throw new OutOfMemoryError();
}

return p;
}

//重新分配内存。将新的native内存块根据字节参数调整大小
public long reallocateMemory(long address, long bytes) {
reallocateMemoryChecks(address, bytes);
//请求大小为0,释放内存
if (bytes == 0) {
freeMemory(address);
return 0;
}
//地址为0,执行内存分配
long p = (address == 0) ? allocateMemory0(bytes) : reallocateMemory0(address, bytes);
if (p == 0) {
throw new OutOfMemoryError();
}

return p;
}

//将给定内存块中的字节设置为固定值,通常为0。通过两个参数确定块的基址,当对象引用为空时,偏移量提供绝对基址。
public void setMemory(Object o, long offset, long bytes, byte value) {
//检查指针是否为有效的基元数组类型指针,检查bytes大小是否有效
setMemoryChecks(o, offset, bytes, value);

if (bytes == 0) {
return;
}

setMemory0(o, offset, bytes, value);
}

//拷贝内存,通过两个参数确定块的基址,当对象引用为空时,偏移量提供绝对基址。
public void copyMemory(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes) {
copyMemoryChecks(srcBase, srcOffset, destBase, destOffset, bytes);//检查

if (bytes == 0) {
return;
}

copyMemory0(srcBase, srcOffset, destBase, destOffset, bytes);
}

//释放内存
public void freeMemory(long address) {
freeMemoryChecks(address);

if (address == 0) {
return;
}

freeMemory0(address);
}

利用 copyMemory() 方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现 clone() 方法,当然这通用的方法只能做到对象浅拷贝。

二. 非常规的对象实例化

1
2
3
4
5
6
7
8
9
10
@ForceInline
public Object allocateInstance(Class<?> cls)
throws InstantiationException {//分配实例,但不运行任何构造函数,若未初始化则初始化类
return theInternalUnsafe.allocateInstance(cls);
}

@HotSpotIntrinsicCandidate
public native Object allocateInstance(Class<?> cls)
throws InstantiationException;

allocateInstance() 方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance() 方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。

这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

三. 操作类、对象、变量

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//任何给定的域都将始终具有相同的偏移量,同个类中有区别的域偏移量永不相同
@ForceInline
public long staticFieldOffset(Field f) {//静态域偏移
return theInternalUnsafe.staticFieldOffset(f);
}

public long staticFieldOffset(Field f) {//报告给定静态域的位置
if (f == null) {
throw new NullPointerException();
}

return staticFieldOffset0(f);
}

@ForceInline
public long objectFieldOffset(Field f) {//对象域偏移
return theInternalUnsafe.objectFieldOffset(f);
}
public long objectFieldOffset(Field f) {
if (f == null) {
throw new NullPointerException();
}

return objectFieldOffset0(f);
}


@Deprecated(since="9", forRemoval=true)
@ForceInline
public Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain) {//定义类,已废弃
return theInternalUnsafe.defineClass(name, b, off, len, loader, protectionDomain);
}

@ForceInline
public Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches) {//定义匿名类,不告知类加载器和系统字典。
return theInternalUnsafe.defineAnonymousClass(hostClass, data, cpPatches);
}
public Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches) {
if (hostClass == null || data == null) {
throw new NullPointerException();
}
if (hostClass.isArray() || hostClass.isPrimitive()) {
throw new IllegalArgumentException();
}

return defineAnonymousClass0(hostClass, data, cpPatches);
}

@ForceInline
public boolean shouldBeInitialized(Class<?> c) {//确保类初始化
return theInternalUnsafe.shouldBeInitialized(c);
}
public boolean shouldBeInitialized(Class<?> c) {
if (c == null) {
throw new NullPointerException();
}

return shouldBeInitialized0(c);
}

通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

四. 数组操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@ForceInline
public int arrayBaseOffset(Class<?> arrayClass) {//获取数组第一个元素的偏移地址
return theInternalUnsafe.arrayBaseOffset(arrayClass);
}
public int arrayBaseOffset(Class<?> arrayClass) {
if (arrayClass == null) {
throw new NullPointerException();
}

return arrayBaseOffset0(arrayClass);
}

@ForceInline
public int arrayIndexScale(Class<?> arrayClass) {//获取数组中元素的增量地址
return theInternalUnsafe.arrayIndexScale(arrayClass);
}
public int arrayIndexScale(Class<?> arrayClass) {
if (arrayClass == null) {
throw new NullPointerException();
}

return arrayIndexScale0(arrayClass);
}

arrayBaseOffsetarrayIndexScale 配合起来使用,就可以定位数组中每个元素在内存中的位置。

由于Java的数组最大值为 Integer.MAX_VALUE ,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。

五. 多线程同步

包括锁机制、CAS操作等。

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
27
28
29
30
31
/**
* 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。
*
* @param obj 需要更新的对象
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值
* @return 如果field的值被更改返回true
*/
@ForceInline
public final boolean compareAndSwapInt(Object o, long offset,
int expected,
int x) {//原子级的更新
return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
}
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);

@ForceInline
public final boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x) {
return theInternalUnsafe.compareAndSetObject(o, offset, expected, x);
}
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetObject(Object o, long offset,
Object expected,
Object x);

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

六. 挂起与恢复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@ForceInline
public void park(boolean isAbsolute, long time) {//阻塞当前线程
theInternalUnsafe.park(isAbsolute, time);
}
@HotSpotIntrinsicCandidate
public native void park(boolean isAbsolute, long time);

@ForceInline
public void unpark(Object thread) {//取消给定线程因调用park()的阻塞,若未阻塞则会使之后的调用park()阻塞
theInternalUnsafe.unpark(thread);
}
@HotSpotIntrinsicCandidate
public native void unpark(Object thread);

park返回的原因有多种包括:

  • 其它线程 unpark 了当前线程
  • 时间到了自然醒( park 有时间参数)
  • 其它线程 interrupt 了当前线程
  • 其它未知原因导致的「假醒」

将一个线程进行挂起是通过 park() 方法实现的,调用 park() 后,线程将一直阻塞直到超时或者中断等条件出现。

unpark() 可以终止一个挂起的线程,使其恢复正常。

这两个方法在底层同过操作系统提供的信号量机制来实现

整个并发框架中对线程的挂起操作被封装在 LockSupport 类中,LockSupport 类中有各种版本park方法,但最终都调用了 Unsafe.park() 方法。

1
2
3
4
5
6
class Thread {
...
volatile Object parkBlocker;
...
}

当线程被 unpark 唤醒后,parkBlocker 会被置为 null。Unsafe.parkunpark 并不会帮我们设置 parkBlocker 属性,负责管理这个属性的工具类是 LockSupport ,它对Unsafe这两个方法进行了简单的包装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LockSupport {
...
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, null); // 醒来后置null
}

public static void unpark(Thread thread) {
if (thread != null)
U.unpark(thread);
}
}
...
}

Java的锁数据结构正是通过调用 LockSupport 来实现休眠与唤醒的。线程对象里面的 parkBlocker 字段的值就是「排队管理器」,具体原理可以参考《队列同步器AQS的实现原理》。

七. 内存屏障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//确保Fence前的装载和存储不会和Fence后的装载和存储被重排序
@ForceInline
public void loadFence() {
theInternalUnsafe.loadFence();
}
@HotSpotIntrinsicCandidate
public native void loadFence();

@ForceInline
public void storeFence() {
theInternalUnsafe.storeFence();
}
@HotSpotIntrinsicCandidate
public native void storeFence();

@ForceInline
public void fullFence() {
theInternalUnsafe.fullFence();
}
@HotSpotIntrinsicCandidate
public native void fullFence();

这是在 Java 8 新引入的,用于定义内存屏障,避免代码重排序。

  • loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。
  • 同理 storeFence() 表示该方法之前的所有store操作在内存屏障之前完成。
  • fullFence() 表示该方法之前的所有load、store操作在内存屏障之前完成。

参考博客和文章书籍等:

说一说Java的Unsafe类

Unsafe与CAS

打通 Java 任督二脉 —— 并发数据结构的基石

因博客主等未标明不可引用,若部分内容涉及侵权请及时告知,我会尽快修改和删除相关内容