Java 语言的一大特点就是跨平台,并且提供的有一套完美的内存管理机制。但这都是 JVM 提供的,如果我们想要直接访问系统内存资源、自主管理内存资源等就无法实现。于是 Java 又提供了一个魔法类:Unsafe。
Unsafe 类位于 sun.misc 包中。从名字看,这个类就是一个不安全的类,实际上它确实是封装了一些不安全的操作!
Unsafe 类和 String 类一样的被定义为 final,也就是说它不可以被继承。并且 Unsafe 被设计成了单例,构造函数是私有的,只能通过 getUnsafe 方法获得它。除此之外,getUnsafe 方法还设置了限制条件,只有授信的代码才能获得该类的实例。哪些是授信的代码呢?当然是 JDK 库里面的类是可以随意使用的。
public final class Unsafe {
// 单例对象
private static final Unsafe theUnsafe;
private Unsafe() {}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 仅在引导类加载器`BootstrapClassLoader`加载时才合法
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
...... 省略其他代码
}
说了半天,这个类,我们无法使用,你讲它又何意义?
别急,Java 虽然不建议我们使用它,但是我们还是可以通过两种方式来使用它。
第一种方式是:让我们的代码在启动时“授信”。在运行程序时,指定 bootclasspath 选项,让你使用 Unsafe 实例的类被引导类加载器加载,从而通过 Unsafe.getUnsafe 方法安全的获取 Unsafe 实例。
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.xttblog.UnsafeTest
这个做法比较少用,所以推荐大家采用第二种方法:通过反射来使用它。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
注意有的 IDE 可能支持的不是很友好。比如:eclipse 显示”Access restriction…”错误,但如果你运行代码,它将正常运行。如果这个错误提示令人烦恼,可以通过以下设置来避免:
Preferences -> Java -> Compiler -> Errors/Warnings ->
Deprecated and restricted API -> Forbidden reference -> Warning
Unsafe 有 8 大功能,很多号主只讲了它的 CAS 功能。
如上图所示,Unsafe 提供的 105 个 API 大致可分为内存操作、CAS、Class 相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等。今天我先来说两个大功能:CAS 和内存操作(和我前面的《手把手教你通过Java代码体验强引用、软引用、弱引用、虚引用的区别》、《90%的程序员可能都不了解的堆外内存》都有些关联,这是一个系列)。
CAS 操作主要涉及到下面 3 个 API。
/**
* CAS
* @param o 包含要修改field的对象
* @param offset 对象中某field的偏移量
* @param expected 期望值
* @param update 更新值
* @return true | false
*/
public final native boolean compareAndSwapObject(Object o,
long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o,
long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o,
long offset, long expected, long update);
CAS 即比较并替换,实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS 是一条 CPU 的原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法(如 compareAndSwapXXX)底层实现即为 CPU 指令 cmpxchg。
CAS 在 java.util.concurrent.atomic 相关类、Java AQS、CurrentHashMap 等实现上有非常广泛的应用。比如,在 AtomicInteger 的实现中,静态字段 valueOffset 即为字段 value 的内存偏移地址,valueOffset 的值在 AtomicInteger 初始化时,在静态代码块中通过 Unsafe 的 objectFieldOffset 方法获取。在 AtomicInteger 中提供的线程安全方法中,通过字段 valueOffset 的值可以定位到 AtomicInteger 对象中 value 的内存地址,从而可以根据 CAS 实现对 value 字段的原子操作。
比如,下图就为某个 AtomicInteger 对象自增操作前后的内存示意图,对象的基地址 baseAddress=“0x110000”,通过 baseAddress+valueOffset 得到 value 的内存地址 valueAddress=“0x11000c”;然后通过 CAS 进行原子性的更新操作,成功则返回,否则继续重试,直到更新成功为止。
说完 CAS,我们再来说说 Unsafe 的内存操作。
内存操作主要有下面 9 个 API。
//分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase,
long srcOffset, Object destBase, long destOffset, long bytes);
//获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
public native byte getByte(long address);
//为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)
public native void putByte(long address, byte x);
在《手把手教你通过Java代码体验强引用、软引用、弱引用、虚引用的区别》和《90%的程序员可能都不了解的堆外内存》两篇文章中,我已经讲过了。在 Java 中创建的对象都处于堆内内存(heap)中,堆内内存是由 JVM 所管控的 Java 进程内存,并且它们遵循 JVM 的内存管理机制,JVM 会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存,存在于 JVM 管控之外的内存区域,Java 中对堆外内存的操作,依赖于 Unsafe 提供的操作堆外内存的 native 方法。
使用堆外内存的原因是:
- 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是 JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在 GC 时减少回收停顿对于应用的影响。
- 提升程序 I/O 操作的性能。通常在 I/O 通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。
我前面提到的 DirectByteBuffer,在 Netty、MINA 等 NIO 框架中应用广泛。DirectByteBuffer 对于堆外内存的创建、使用、销毁等逻辑均由 Unsafe 提供的堆外内存 API 来实现。
上图为 DirectByteBuffer 构造函数,创建 DirectByteBuffer 的时候,通过 Unsafe.allocateMemory 分配内存、Unsafe.setMemory 进行内存初始化,而后构建 Cleaner 对象用于跟踪 DirectByteBuffer 对象的垃圾回收,以实现当 DirectByteBuffer 被垃圾回收时,分配的堆外内存一起被释放。具体的释放就是我前面讲的 PhantomReference 虚引用。
以上就是 Unsafe 类 8 大主要功能的 2 个重要的功能。其他功能,我会在对应使用到的框架脑图中串起来讲。当然,如果你们现在希望了解的话,我也可以提前写一下这方便的内容。选择权在于你们的留言和评论!
: » Unsafe 的 CAS 和内存操作的原理、源码解毒
原创文章,作者:kirin,如若转载,请注明出处:https://blog.ytso.com/252068.html