Unsafe 类详解


众所周知 Java 作为高级开发语言基于 C 实现,屏蔽了指针方式的内存直接操作,但在 JDK 中仍然留了一条路。

Unsafe 类,通过其可以直接访问堆外内存,即不再受 GC 管控。显然也增加了内存泄漏的风险,因此也是其名为 Unsafe 的一大原因。

1. 对象实例

如果仔细看 Unsafe 类内部定义,可以看到仅有一个无参构造函数且声明为私有。

显然 JDK 并不希望谁都可以轻易访问,因此实例化 Unsafe 只能另辟蹊径通过反射的方式获取。

在上述代码可以看到 Unsafe 内部定义了静态成员属性 theUnsafe,我们则可以此通过反射获取实例,代码如下:

public void init() throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
}

2. CAS操作

在获取 unsafe 实例之后即可利用其实现一系列操作。

其中最常见的即 CAS(Compare And Swap) 原子操作,详细内容参考下述代码:

public static class MyEntity {

    public String name;

    public MyEntity(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public void info(Unsafe unsafe) throws NoSuchFieldException {
    MyEntity entity = new MyEntity("Alex");
    long offset = unsafe.objectFieldOffset(MyEntity.class.getDeclaredField("name"));
    
    // Get object value
    String str = (String) unsafe.getObject(entity, offset);
    System.out.println(str);

    // Put object value
    unsafe.putObject(entity, offset, "Beth");
    System.out.println(entity.getName());

    unsafe.compareAndSwapObject(entity, offset, "Beth", "Jack");
    System.out.println(entity.getName());
}

3. 内存访问

Unsafe 之所以被称为非安全的一大重要原因即它提供了直接操作内存的能力。

那就让我们看一下它究竟是怎么个操作法,针对内存操作的常用方法如下:

方法 作用
allocateMemory() 分配一块连续且不受堆内存与 GC的管控的直接内存块,,返回内存块的起始地址。
freeMemory(address) 根据传入的内存地址释放内存资源。
putByte(address, data) 在指定内存处写入一字节数据。
getByte(address, data) 在指定内存处读取一字节数据。

如下述示例即通过 unsafe 对象手动声明了一块直接内存,并存储一个 entity 对象。

其中 convertObjbyteToObject 即利用 ByteArrayInputStreamByteArrayOutputStreamJava 对象进行二进制的转化,因为内存中存储的数据必须为字节数据。

public void memoryDemo(Unsafe unsafe) {
    byte[] data = "Hello Budai!".getBytes();
    // allocate native memory
    long address = unsafe.allocateMemory(data.length);

    try{
        // put data to memory
        for (int i = 0; i < data.length; i++) {
            unsafe.putByte(address + i, data[i]);
        }

        byte[] origin = new byte[data.length];
        for (int i = 0; i < origin.length; i++) {
            // read data from native memory
            origin[i] = unsafe.getByte(address + i);
        }
        System.out.println("Result: " + new String(origin));
    } catch (Exception ignored) {
    } finally {
        // release the memory
        unsafe.freeMemory(address);
    }
}

4. 内存对齐

Unsafe 中通过 allocateMemory() 申请的内存属于直接内存,是未经任何初始化的内存空间。

在计算机系统中,有些硬件操作要求数据以特定的字节对齐方式存储,这可以提高数据读取和写入的效率。因此对于 allocateMemory() 申请的内存空间,通常为了更好的性能会利用空数据进行对齐填充操作,以确保数据存储在内存中时满足特定的对齐要求,进而提高了程序的执行效率。

如下示例即对与 unsafe 申请的内存空间进行对齐填充操作。

private static Unsafe unsafe;

private static long address;

private static byte[] data;

private void preFillData(byte[] data) {
    int position = 0;
    int dataLen = data.length;
    for (; dataLen - position >= 8; position++) {
        unsafe.putLong(address + position, 0L);
    }

    for (; dataLen - position >= 4; position++) {
        unsafe.putInt(address + position, 0);
    }

    for (; dataLen - position >= 0; position++) {
        unsafe.putByte(address + position, (byte) 0);
    }
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录