微前端架构如何改变企业的开发模式与效率提升
870
2022-11-04
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
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
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~