Steve's Blog

Talk is cheap, show me the code.

0%

Unsafe类

Unsafe是一个sun.misc包下的类。Unsafe为我们提供了访问底层的机制,这种机制仅供java核心类库使用,而不应该被普通用户使用。但是,为了更好地了解java的生态体系,我们应该去学习它,去了解它,不求深入到底层的C/C++代码,但求能了解它的基本功能。

1. 获取Unsafe对象

由于JVM默认不允许外部使用Unsafe

1
2
3
4
5
6
7
8
9
10
11
12
private static final Unsafe theUnsafe;

@CallerSensitive
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
10
private 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
2
3
4
/**
* 传入一个Class对象,生成一个Class对象对应类型的对象实例,底层不会调用类的构造方法
*/
public native Object allocateInstance(Class<?> var1) throws InstantiationException;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public 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
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
/** 
* 返回给定的静态属性在它的类的存储分配中的位置(偏移地址)
*/
public native long staticFieldOffset(Field var1);
/**
* 返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)
*/
public native long objectFieldOffset(Field var1);
/**
* 返回给定的静态属性的位置,配合staticFieldOffset方法使用
*/
public native Object staticFieldBase(Field var1);

/**
* 返回数组类型的第一个元素的偏移地址(基础偏移地址)
*/
public native int arrayBaseOffset(Class<?> var1);
/**
* 返回数组中元素与元素之间的偏移地址的增量
* 与arrayBaseOffset方法配合使用就可以定位到任何一个元素的地址
* 在CHM(ConcurrentHashMap中就有用到)
*/
public native int arrayIndexScale(Class<?> var1);

/**
* 获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法
*/
public native int addressSize();
/**
* 获取本地内存的页数,此值为2的幂次方
*/
public native int pageSize();

c. 抛出checked异常,而且不在方法签名中定义异常

1
2
3
4
/**
* 抛出一个checked异常
*/
public native void throwException(Throwable var1);

d. 使用堆外内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  
/**
* 分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址
*/
public native long allocateMemory(long var1);
/**
* 通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)
*/
public native long reallocateMemory(long var1, long var3);
/**
* 将给定内存块中的所有字节设置为固定值(通常是0)
*/
public native void setMemory(Object var1, long var2, long var4, byte var6);
/**
* 释放给定地址的内存
*/
public native void freeMemory(long var1);

测试方法

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
class 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
2
3
4
5
6
7
8
/**
* 当一个线程正在等待某个操作时,JVM调用Unsafe的park()方法来阻塞此线程。
*/
public native void park(boolean var1, long var2);
/**
* 当阻塞中的线程需要再次运行时,JVM调用Unsafe的unpark()方法来唤醒此线程。
*/
public native void unpark(Object var1);

5. 使用Unsafe的CAS功能

使用Unsafe的CAS功能实现一个原子类AtomicInteger

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
public 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() {
@Override
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;
}
}

参考文档

[1] 死磕 java魔法类之Unsafe解析
[2] Java多线程深入学习-Unsafe类的介绍