简介

Unsafe是位于sun.misc包下的一个类,次要提供一些用于执行低级别、不平安操作的办法,如间接拜访零碎内存资源、自主治理内存资源等,这些办法在晋升Java运行效率、加强Java语言底层资源操作能力方面起到了很大的作用。但因为Unsafe类使Java语言领有了相似C语言指针一样操作内存空间的能力,这无疑也减少了程序产生相干指针问题的危险。在程序中适度、不正确应用Unsafe类会使得程序出错的概率变大,使得Java这种平安的语言变得不再“平安”,因而对Unsafe的应用肯定要谨慎。

获取Unsafe实例

private static sun.misc.Unsafe getUnsafe() {        try {            return AccessController.doPrivileged(new PrivilegedExceptionAction<Unsafe>() {                @Override                public sun.misc.Unsafe run() throws Exception {                    Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;                    for (Field f : k.getDeclaredFields()) {                        f.setAccessible(true);                        Object x = f.get(null);                        if (k.isInstance(x)) {                            return k.cast(x);                        }                    }                    // The sun.misc.Unsafe field does not exist.                    throw new Error("unsafe is null");                }            });        } catch (Throwable e) {            throw new Error("get unsafe failed", e);        }    }

Unsafe性能列表

  • allocateMemory/freeMemory,调配、开释堆外内存DirectMemory(和c/cpp中的malloc一样)
  • CAS操作
  • copyMemory
  • defineClass(without security checks)
  • get/put address 应用堆外内存地址进行数据的读写操作
  • get/put volatile 应用堆外内存地址进行数据的读写操作 - volatile版本
  • loadFence/storeFence/fullFence 禁止指令重排序
  • park/unpark 阻塞/解除阻塞线程

Unsafe的数组操作

unsafe中,有两个对于数组的办法:

public native int arrayBaseOffset(Class<?> arrayClass);public native int arrayIndexScale(Class<?> arrayClass);

base offset含意

首先,在Java中,数组也是对象

In the Java programming language, arrays are objects (§4.3.1), are dynamically created, and may be assigned to variables of type Object (§4.3.2). All methods of class Object may be invoked on an array.
https://docs.oracle.com/javase/specs/jls/se7/html/jls-10.html

那么既然是对象,就会有object header,占用一部分空间,那么了解数组的base offset也就不难了

比方上面的一段JOL输入,实际上对象的属性数据是从OFFSET 16的地位开始的,0-12的空间被header所占用

HotSpot 64-bit VM, COOPS, 8-byte alignmentlambda.Book object internals: OFFSET  SIZE           TYPE DESCRIPTION                               VALUE      0    12                (object header)                           N/A     12     4            int Book.sales                                N/A     16     4         String Book.title                                N/A     20     4      LocalDate Book.publishTime                          N/A     24     4         String Book.author                               N/A     28     4   List<String> Book.tags                                 N/AInstance size: 32 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total

那么如果要拜访对象的属性数据,须要基于基地址(base address)进行偏移,基地址+根底偏移(base offset)+属性偏移(field offset)才是数据的内存地址(逻辑),那么title属性的内存地址实际上就是:

book(instance).title field address = book object base address + base offset + title field offset

数组在Java里能够视为一种非凡的对象,无论什么类型的数组,他们在内存中都会有一分根底偏移的空间,和object header相似

通过测试,64位的JVM数组类型的根底偏移都是16(测试后果在不同JVM下可能会有所区别)
原始类型的根底偏移都是12(测试后果在不同JVM下可能会有所区别)
能够应用JOL工具查看原始类型包装类的offset,比方int就看Integer的,尽管jdk没有提供函数,然而通过JOL也是能够获取的,jvm类型和对其形式匹配即可
**

index scale含意

就是指数组中每个元素所占用的空间大小,比方int[] scale就是4,long[] scale就是8,object[] scale就是4(指针大小)

有了这个offset,就能够对数组进行copyMemory操作了

public native void copyMemory(Object srcBase, long srcOffset,                                  Object destBase, long destOffset,                                  long bytes);

array copy to direct memory

在应用copyMemory操作时,须要传入对象及对象的base offset,对于数组来说,offset就是下面介绍的offset,比方当初将一个数组中的数据拷贝至DirectBuffer

byte[] byte = new byte[4096];unsafe.copyMemory(byte,ARRAY_BYTE_BASE_OFFSET,null,directAddr,4096);

之所以应用unsafe而不是ByteBuffer的办法来操作DirectBuffer,是因为ByteBuffer不够灵便。

比方我想把一个byte[]拷贝至DirectBuffer的某个地位中,就没有相应的办法;只能先设置position,而后再put(byte[], int, int),十分麻烦,而且并发拜访时position(long)和put也是个非原子性操作

然而用unsafe来操作的话就很轻松了,间接copyMemory,间接指定address + offset就行

unsafe.copyMemory(byte,ARRAY_BYTE_BASE_OFFSET,null,directAddr,4096);

copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes)

  • srcBase 原数据对象,能够是对象、数组(对象),也能够是Null(为Null时必须指定offset,offset就是address)
  • srcOffset 原数据对象的base offset,如果srcBase为空则此项为address
  • destBase 指标数据对象,规定同src
  • destOffset 指标数据对象的base offset,规定同src
  • bytes 要拷贝的数据大小(字节单位)

通过copyMemory办法,能够做各种拷贝操作:

对象(个别是数组)拷贝到指定堆外内存地址

long l = unsafe.allocateMemory(1);data2[0] = 5;//指标是memory address,destBase为null,destOffset为addressunsafe.copyMemory(data2,16,null,l,1);

将对象拷贝到对象

byte[] data1 = new byte[1];data1[0] = 9;byte[] data2 = new byte[1];unsafe.copyMemory(data1,16,data2,16,1);

将堆外内存地址的数据拷贝到堆内(个别是数组)

byte[] data2 = new byte[1];long l = unsafe.allocateMemory(1);unsafe.putByte(l, (byte) 2);//源数据是memory address,srcBase为null,srcOffset为addressunsafe.copyMemory(null,l,data2,16,1);

堆外内存地址相互拷贝

long l = unsafe.allocateMemory(1);long l2 = unsafe.allocateMemory(1);unsafe.putByte(l, (byte) 2);//源数据是memory address,srcBase为null,srcOffset为address//指标是memory address,destBase为null,destOffset为addressunsafe.copyMemory(null,l,null,l2,1);

Benchmark

sun.misc.Unsafe#putInt(java.lang.Object, long, int) & object field manual set

禁用JIT后果:Benchmark                           Mode  Cnt        Score   Error   UnitsObjectFieldSetBenchmark.manualSet  thrpt    2  8646455.472          ops/nsObjectFieldSetBenchmark.unsafeSet  thrpt    2  7901066.170          ops/ns
启用JIT后果:Benchmark                           Mode  Cnt          Score   Error   UnitsObjectFieldSetBenchmark.manualSet  thrpt    2  477232013.545          ops/nsObjectFieldSetBenchmark.unsafeSet  thrpt    2  499135982.962          ops/ns

论断,简直没区别

什么时候用Unsafe

个别应用DirectBuffer时,须要配合Unsafe来应用,因为DirectBuffer的内存是调配在JVM Heap之外的,属于C Heap,所以须要用间接操作内存地址(逻辑),和C里malloc之后的操作形式一样**