广告位联系
返回顶部
分享到

sun unsafe类功能及使用注意事项介绍

java 来源:互联网 作者:秩名 发布时间:2022-01-26 12:57:54 人浏览
摘要

Unsafe简介 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Ja

Unsafe简介

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

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

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

获取Unsafe实例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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中,有两个关于数组的方法:

1

2

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所占用

1

2

3

4

5

6

7

8

9

10

11

12

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<String> 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属性的内存地址实际上就是:

1

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操作了

1

2

3

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

1

2

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就行

1

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方法,可以做各种拷贝操作:

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

1

2

3

4

long l = unsafe.allocateMemory(1);

data2[0] = 5;

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

unsafe.copyMemory(data2,16,null,l,1);

将对象拷贝到对象

1

2

3

4

5

byte[] data1 = new byte[1];

data1[0] = 9;

 

byte[] data2 = new byte[1];

unsafe.copyMemory(data1,16,data2,16,1);

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

1

2

3

4

5

6

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);

堆外内存地址互相拷贝

1

2

3

4

5

6

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结果:

Benchmark Mode Cnt Score Error Units
ObjectFieldSetBenchmark.manualSet thrpt 2 8646455.472   ops/ns
ObjectFieldSetBenchmark.unsafeSet thrpt 2 7901066.170   ops/ns

启用JIT结果:

Benchmark Mode Cnt Score Error Units
ObjectFieldSetBenchmark.manualSet thrpt 2 477232013.545   ops/ns
ObjectFieldSetBenchmark.unsafeSet thrpt 2 499135982.962   ops/ns

结论,几乎没区别

什么时候用Unsafe

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


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。

您可能感兴趣的文章 :

原文链接 : https://heapdump.cn/article/2062595
    Tag :
相关文章
  • SpringBoot自定义错误处理逻辑介绍

    SpringBoot自定义错误处理逻辑介绍
    1. 自定义错误页面 将自定义错误页面放在 templates 的 error 文件夹下,SpringBoot 精确匹配错误信息,使用 4xx.html 或者 5xx.html 页面可以打印错误
  • Java实现手写一个线程池的代码

    Java实现手写一个线程池的代码
    线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和
  • Java实现断点续传功能的代码

    Java实现断点续传功能的代码
    题目实现:网络资源的断点续传功能。 二、解题思路 获取要下载的资源网址 显示网络资源的大小 上次读取到的字节位置以及未读取的字节
  • 你可知HashMap为什么是线程不安全的
    HashMap 的线程不安全 HashMap 的线程不安全主要体现在下面两个方面 在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况 在
  • ArrayList的动态扩容机制的介绍

    ArrayList的动态扩容机制的介绍
    对于 ArrayList 的动态扩容机制想必大家都听说过,之前的文章中也谈到过,不过由于时间久远,早已忘却。 所以利用这篇文章做做笔记,加
  • JVM基础之字节码的增强技术介绍

    JVM基础之字节码的增强技术介绍
    字节码增强技术 在上文中,着重介绍了字节码的结构,这为我们了解字节码增强技术的实现打下了基础。字节码增强技术就是一类对现有字
  • Java中的字节码增强技术

    Java中的字节码增强技术
    1.字节码增强技术 字节码增强技术就是一类对现有字节码进行修改或者动态生成全新字节码文件的技术。 参考地址 2.常见技术 技术分类 类
  • Redis BloomFilter布隆过滤器原理与实现

    Redis BloomFilter布隆过滤器原理与实现
    Bloom Filter 概念 布隆过滤器(英语:Bloom Filter)是1970年由一个叫布隆的小伙子提出的。它实际上是一个很长的二进制向量和一系列随机映射
  • Java C++算法题解leetcode801使序列递增的最小交换次

    Java C++算法题解leetcode801使序列递增的最小交换次
    题目要求 思路:状态机DP 实现一:状态机 Java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minSwap(int[] nums1, int[] nums2) { int n
  • Mybatis结果集映射与生命周期介绍

    Mybatis结果集映射与生命周期介绍
    一、ResultMap结果集映射 1、设计思想 对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了 2、resultMap的应用场
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计