众所周知 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
对象。
其中 convertObj
与 byteToObject
即利用 ByteArrayInputStream
与 ByteArrayOutputStream
对 Java
对象进行二进制的转化,因为内存中存储的数据必须为字节数据。
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);
}
}