众所周知,Java成为现在广泛使用的后端语言之一就是因为其拥有自动垃圾回收机制,由于垃圾回收的消耗,也导致Java运行效率不如没有垃圾回收机制的C++等,本文介绍Java中“不安全”的部分—手动申请和释放内存

Unsafe类

首先看一下Unsafe类注释

A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it. Note: It is the responsibility of the caller to make sure arguments are checked before methods of this class are called. While some rudimentary checks are performed on the input, the checks are best effort and when performance is an overriding priority, as when methods of this class are optimized by the runtime compiler, some or all checks (if any) may be elided. Hence, the caller must not rely on the checks and corresponding exceptions!

执行低级不安全操作的方法集合。尽管该类和所有方法都是公共的,但该类的使用受到限制,因为只有受信任的代码才能获得它的实例。注意:调用方有责任确保在调用此类的方法之前检查参数。虽然对输入执行了一些基本检查,但这些检查是尽最大努力的,当性能是压倒一切的优先级时,例如当此类的方法由运行时编译器优化时,可以省略一些或所有检查(如果有)。因此,调用方不能依赖于检查和相应的异常!

可以看到两点:

  • Unsafe类是低级不安全操作的方法
  • 只有收到信任的代码才能获得实例

下面是类中提供的获取实例的代码

1
2
3
4
5
6
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}

可以看到,在获取实例时会检查是否是Bootstrap类,只有Bootstrap才能获取实例进行不安全的操作,因此,一般我们有两种方法获取Unsafe的实例

通过jvm参数设置引导类

可以在启动时加上

1
-Xbootclasspath/p:path

从而在设置的类中调用

1
Unsafe.getUnsafe();

获取Unsafe的实例,这种方法比较麻烦,因此接下来通过Java反射机制获取。

通过反射获取

我们观察到Unsafe类中维护了一个theUnsafe实例

1
private static final Unsafe theUnsafe = new Unsafe();

因此通过反射可以获取到这个实例

1
2
3
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);

拿到这个类的实例后我们能做什么呢?

可以观察一下这个类提供的方法

Unsafe API的大部分方法都是native实现,主要包括以下几类:

  1. Info相关。主要返回某些低级别的内存信息

  2. Objects相关。主要提供Object和它的域操纵方法

  3. Class相关。主要提供Class和它的静态域操纵方法

  4. Arrays相关。数组操纵方法

  5. Synchronization相关。 主要提供低级别同步原语(如基于CPU的CAS (Compare-And-Swap) 原语)

  6. Memory相关。直接内存访问方法,绕过JVM堆直接操纵本地内存

应用场景

使String可变

上面这种操作是这样实现的

1
2
3
4
5
6
7
8
9
10
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
Class<String> clazz = String.class;
Field value = clazz.getDeclaredField("value");
long offset = unsafe.objectFieldOffset(value);

final String s = "123";
unsafe.putObject(s, offset, new byte[]{'6', '6', '6'});
System.out.println(s);

通过Unsafe类,我们可以修改被标记为final的对象

手动申请和释放内存

1
2
3
4
5
long address = unsafe.allocateMemory(4 * Integer.SIZE);
unsafe.putInt(address, 12345);
int value = unsafe.getInt(address);
unsafe.freeMemory(address);
System.out.println(value);

其他的如内存拷贝,CAS操作就不一一演示了,基本概念和C++相似