sun unsafe类功能及使用注意事项详解

网友投稿 870 2022-11-04

sun unsafe类功能及使用注意事项详解

sun unsafe类功能及使用注意事项详解

Unsafe简介

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。

但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。

在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。

获取Unsafe实例

private static sun.misc.Unsafe getUnsafe() {

try {

return AccessController.doPrivileged(new PrivilegedExceptionAction() {

@Override

public sun.misc.Unsafe run() throws Exception {

Class 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操作copyMemorydefineClass(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 alignment

lambda.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 Book.tags N/A

Instance size: 32 bytes

Space 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为空则此项为addressdestBase 目标数据对象,规则同srcdestOffset 目标数据对象的base offset,规则同srcbytes 要拷贝的数据大小(字节单位)

通过copyMemory方法,可以做各种拷贝操作:

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

long l = unsafe.allocateMemory(1);

data2[0] = 5;

//目标是memory address,destBase为null,destOffset为address

unsafe.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为address

unsafe.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为address

unsafe.copyMemory(null,l,null,l2,1);

Benchmark

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

禁用JIT结果:

BenchmarkModeCntScoreErrorUnitsObjectFieldSetBenchmark.manualSetthrpt28646455.472 ops/nsObjectFieldSetBenchmark.unsafeSetthrpt27901066.170 ops/ns

启用JIT结果:

BenchmarkModeCntScoreErrorUnitsObjectFieldSetBenchmark.manualSetthrpt2477232013.545 ops/nsObjectFieldSetBenchmark.unsafeSetthrpt2499135982.962 ops/ns

结论,几乎没区别

什么时候用Unsafe

一般使用DirectBuffer时,需要配合Unsafe来使用,因为DirectBuffer的内存是分配在JVM Heap之外的,属于C Heap,所以需要用直接操作内存地址(逻辑),和C里malloc之后的操作方式一样

以上就是unsafe类功能及使用注意事项详解的详细内容,更多关于unsafe类功能及使用的资料请关注我们其它相关文章!

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:nxweb- 小巧高效的 HTTP 引擎
下一篇:Ailurus- 小熊猫Ubuntu增强工具
相关文章

 发表评论

暂时没有评论,来抢沙发吧~