Unsafe是一个sun.misc
包下的类。Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本功能。
1. 获取Unsafe对象
由于JVM默认不允许外部使用Unsafe1
2
3
4
5
6
7
8
9
10
11
12private static final Unsafe theUnsafe;
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 判断当前类加载器不为系统加载器,抛出一个安全异常
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
需要使用反射获取Unsafe内部持有的Unsafe对象theUnsafe
1
2
3
4
5
6
7
8
9
10private static Unsafe UNSAFE;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
CLogger.writeError("title", e);
}
}
2. Unsafe功能
a. 实例化类
1 | /** |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14public class UnsafeAllocateInstanceTest {
static class User {
int age;
public User() {
age = 10;
}
}
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.age); // 打印结果为10
User u2 = (User) UNSAFE.allocateInstance(User.class);
System.out.println(u2.age); // 打印结果为0
}
}
b. 获取内存偏移地址
1 | /** |
c. 抛出checked异常,而且不在方法签名中定义异常
1 | /** |
d. 使用堆外内存
1 |
|
测试方法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
45class OffHeapArray {
// 一个int等于4个字节
private static final int INT = 4;
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private long size;
private long address;
// 构造方法,分配内存
public OffHeapArray(long size) {
this.size = size; // 参数字节数
address = unsafe.allocateMemory(size * INT);
}
// 获取指定索引处的元素
public int get(long i) {
return unsafe.getInt(address + i * INT);
}
// 设置指定索引处的元素
public void set(long i, int value) {
unsafe.putInt(address + i * INT, value);
}
// 元素个数
public long size() {
return size;
}
// 释放堆外内存
public void freeMemory() {
unsafe.freeMemory(address);
}
}
e. CAS方法
CAS (compare and swap)比较并替换,这是一个原子操作,底层实现使用了汇编中cmpxchange
指令,传入4个参数:要修改的对象,要修改数据在对象类的相对偏移地址,预期值,想要设置的值。
预期值的作用是使用当前内存中最新值和传入的预期值进行比较,如果相同,则要修改的数据没有发生修改(此处可能会有ABA问题,JUC中提供了两个类处理此类问题,后面详述),直接用想要设置的值进行替换。1
2
3
4
5
6
7
8
9
10
11
12/**
* 使用CAS方式替换一个对象变量
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
/**
* 使用CAS方式替换一个int类型变量
*/
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
/**
* 使用CAS方式替换一个long类型变量
*/
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
f. park / unpark
JVM在上下文切换的时候使用了Unsafe中的方法park()和unpark()。在LockSupport.park()/unpark()方法中,它们底层都是调用的Unsafe的这两个方法。
1 | /** |
5. 使用Unsafe的CAS功能
使用Unsafe的CAS功能实现一个原子类AtomicInteger1
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
65public class MyAtomicInteger {
private static Unsafe UNSAFE;
private static int amount = 0;
private static MyAtomicInteger amountMAI = new MyAtomicInteger();
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
CLogger.writeError("title", e);
}
}
private volatile int val;
public static void main(String[] args) throws InterruptedException {
// right way
for (int i = 0; i < 10000; i++) {
Thread title = new Thread(new Runnable() {
public void run() {
try {
for (int i1 = 0; i1 < 1000; i1++) {
amountMAI.incrAndGet();
}
} catch (NoSuchFieldException e) {
CLogger.writeError("title", e);
}
}
});
title.start();
// 等待线程执行结束
title.join();
}
// 10000000
System.out.println(amountMAI.getCount());
// wrong way
// for (int i = 0; i < 10000; i++) {
// new Thread(new Runnable() {
// @Override
// public void run() {
// for (int i1 = 0; i1 < 1000; i1++) {
// amount++;
// }
// }
// }).start();
// }
// // 281, 790
// System.out.println(amount);
}
public int incrAndGet() throws NoSuchFieldException {
// 使用Unsafe提供的compareAndSwapInt方法,如果设值失败则一直循环,直到设值成功
while (!UNSAFE.compareAndSwapInt(this, UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("val")), this.val, val + 1))
;
return val;
}
public int getCount() {
return val;
}
}