Unsafe类初探

Unsafe类说明

从这个类的名字Unsafe上来说这个类就是一个不安全的类,也是不开放给用户直接使用的(当然我们还是可以通过其他一些方法用到)。
这个类在jdk源码中多个类中用到,主要作用是任意内存地址位置处读写数据,外加一下CAS操作。它的大部分操作都是绕过JVM通过JNI完成的,因此它所分配的内存需要手动free,所以是非常危险的。但是Unsafe中很多(但不是所有)方法都很有用,且有些情况下,除了使用JNI,没有其他方法弄够完成同样的事情。
至于研究它的起因,是因为我最近在看jdk8的ConcurrentHashMap,这个版本的主要函数就是用过Unsafe来完成的。

Unsafe类的调用

Unsafe类是一个单例,调用的方法为getUnsafe,如下。可以看到,虽然是可以调用,但是会有一步判断,判断是不是内部会检查该CallerClass是不是由系统类加载器BootstrapClassLoader加载。由系统类加载器加载的类调用getClassLoader()会返回null,所以要检查类是否为bootstrap加载器加载只需要检查该方法是不是返回null。

1
2
3
4
5
6
7
8
9
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}

那我们怎么能调用到它呢,有以下2中方法。

  1. 通过JVM参数-Xbootclasspath指定要使用的类为启动类;
  2. 在Unsafe类中有一个成员变量theUnsafe,因此我们可以通过反射将private单例实例的accessible设置为true,然后通过Field的get方法获取,如下。
    1
    2
    3
    Field f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    Unsafe unsafe = (Unsafe) f.get(null);

不调用构造方法生成对象

利用Unsafe的allocateInstance方法,可以在未调用构造方法的情况下生成了对象。下面的例子很好的说明了这一点。

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
static class City {
private String name = "";
private int flag = 0;
public City() {
this.name = "Beijing";
this.flag = 1;
}
@Override
public String toString() {
return name + ": " + flag;
}
}
public static void main(String[] args) throws Exception{
// 通过反射得到theUnsafe对应的Field对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置该Field为可访问
field.setAccessible(true);
// 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
Unsafe unsafe = (Unsafe) field.get(null);
City city = (City) unsafe.allocateInstance(City.class);
System.out.println(city); //dont invoke constructor, print null: 0
City anotherCity = new City();
System.out.println(anotherCity); //print Beijing: 1
}

内存修改

我们看一下下面的代码,在正常的情况下sizeValidate始终范围false,但是通过计算MAX_SIZE的位移,将其进行修改之后,就会返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static class Validation {
private int MAX_SIZE = 10;
public boolean sizeValidate() {
return 20 < MAX_SIZE;
}
}
public static void main(String[] args) throws Exception{
// 通过反射得到theUnsafe对应的Field对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置该Field为可访问
field.setAccessible(true);
// 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
Unsafe unsafe = (Unsafe) field.get(null);
Validation v = new Validation();
System.out.println(v.sizeValidate()); // false
Field f = v.getClass().getDeclaredField("MAX_SIZE");
unsafe.putInt(v, unsafe.objectFieldOffset(f), 100); // memory corruption
System.out.println(v.sizeValidate()); // true
}

计算Java对象大小

有两种计算Java对象大小的方式。

  1. 通过java.lang.instrument.Instrumentation的getObjectSize(obj)直接获取对象的大小;
  2. 通过sun.misc.Unsafe对象的objectFieldOffset(field)等方法结合反射来计算对象的大小。

通过Unsafe获取Java对象大小的基本思路如下:

  1. 通过反射获得一个类的Field;
  2. 通过Unsafe的objectFieldOffset()获得每个Field的off。Set;
  3. 对Field进行遍历,取得最大的offset,然后加上这个field的长度,再加上Padding对齐。

这边不写代码了,想要了解的同学可以看一下这个

Java并发中的应用

在Java并发中会用到CAS操作,对应于Unsafe类中的compareAndSwapInt,compareAndSwapLong等。下面的例子就是使用Unsafe实现的无所数据结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class LongValue {
private volatile long counter = 0;
private Unsafe unsafe;
private long offset;
public LongValue() throws Exception {
unsafe = getUnsafe();
offset = unsafe.objectFieldOffset(LongValue.class.getDeclaredField("counter"));
}
public void increment() {
long before = counter;
while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
before = counter;
}
}
public long getCounter() {
return counter;
}
}

看一下Java中AtomicLong的实现,下面摘出来一部分。可以看到该类在加载的时候将value的偏移位置计算出来,然后在compareAndSet等方法中使用Unsafe中的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
...
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
// setup to use Unsafe.compareAndSwapLong for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
...
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile long value;
...
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
...

其他使用方式

  1. 通过Unsafe的defineClass可以动态加载Class;
  2. 通过Unsafe的copyMemoryfreeMemory等可以实现内存的复制与释放,如果我们知道了对象的大小,利用arrayBaseOffsetcopyMemory可以完成对象的浅拷贝。

Unsafe还有其他很多的用途,但是要记得这是一个非常危险的类,在使用的过程中需要万分小心,不到万不得已的情况不要使用。

Jerky Lu wechat
欢迎加入微信公众号