99%的开发者不使用Unsafe类,也可能从未听说过它,但是有1%的开发者使用Unsafe类,这些1%的开发者通常写一些广泛使用的库,使得99%的开发者被传递性地使用Unsafe类(尽管Unsafe类的意图是仅为JDK内部提供服务)。
目前的用户的困境是:不使用Unsafe类会受到诸多限制,其他可选方案很可能是低效的,比如ByteBuffer API。但如果使用Unsafe类,就失去了Java这门安全语言对于安全的保证,用户也可能被Unsafe类的锋利所伤害,导致虚拟机崩溃,代码变得不可移植,或者JDK升级后代码行为发生改变等。基于这些原因,Java社区逐渐为Unsafe类中一些确实有用的方法提供了安全的替代方案。JEP 193引入的VarHandle,它可以替代JUC.atomic和Unsafe类的部分操作,并提供了标准的内存屏障方法。
JEP 370提供了外部内存访问API(JDK15二次孵化)可以安全且高效地访问堆外内存。
尽管如此,在笔者写作本文之时,Unsafe类仍然是很多三方库实现某些少见需求的首选,在它的替代品日臻完善期间,还是有必要单独讨论下Unsafe类。本节剩余内容将简单介绍Unsafe类中的一些重要方法。
Java堆又叫堆内内存,它交由垃圾回收器全权负责,垃圾回收器在其上分配内存、储存对象、释放内存。与之相对的概念是堆外内存(Off-heap),这部分内存不受垃圾回收器控制,由开发者自行负责。调用 java.nio.ByteBuffer.allocateDirect()可以分配堆外内存,allocateDiret()实际上是借助Unsafe.AllocateMemory实现分配堆外内存分配的,如代码清单4-20所示:
代码清单4-20 Unsafe.allocateMemory/freeMemory底层实现
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory0(...)) {
size_t sz = (size_t)size;
sz = align_up(sz, HeapWordSize);
void* x = os::malloc(sz, mtOther);
return addr_to_java(x);
} UNSAFE_END
UNSAFE_ENTRY(void, Unsafe_FreeMemory0(...)) {
void* p = addr_from_java(addr);
os::free(p);
} UNSAFE_END
在底层实现中,调用os::malloc/free完成。os::malloc/free又是调用glibc的malloc/free函数,所以堆外内存是指由malloc直接分配的一片内存,虚拟机的垃圾回收器不会回收这片内存中的对象,这片内存的管理和释放全权交给开发者。
虽然JSR 133删除模型规定了若干需要插入内存屏障的位置,虚拟机很好地完成了这些任务,但是Java层面是不支持内存屏障的,为了支持以后JUC可能出现的新API特性,也为了Java开发者能进行高级并发编程,JEP 171在Unsafe类中增加了内存屏障方法 loadFence/storeFence/fullFence方法,如代码清单4-21所示:
代码清单4-21 Unsafe.loadFence/storeFence/fullFence底层实现
UNSAFE_LEAF(void, Unsafe_LoadFence(JNIEnv *env, jobject unsafe)) {
OrderAccess::acquire();
} UNSAFE_END
UNSAFE_LEAF(void, Unsafe_StoreFence(JNIEnv *env, jobject unsafe)) {
OrderAccess::release();
} UNSAFE_END
UNSAFE_LEAF(void, Unsafe_FullFence(JNIEnv *env, jobject unsafe)) {
OrderAccess::fence();
} UNSAFE_END
通过调用它们可以直接使用内存屏障指令,如x86的lfence、sfence和mfence。
java.util.concurrent(简称JUC)包含很多并发组件,这些并发组件可以阻塞线程的执行,唤醒线程。这些行为的背后都依赖 java.util.concurrent.locks.LockSupport的park/unpark方法,而LockSupport.park/unpark又最终调用Unsafe.park/unpark实现其功能,如代码清单4-22所示:
代码清单4-22 Unsafe.park/unpark底层实现
UNSAFE_ENTRY(void, Unsafe_Park(...)) {
...
thread->parker()->park(isAbsolute != 0, time);
} UNSAFE_END
UNSAFE_ENTRY(void, Unsafe_Unpark(...)) {
Parker* p = NULL;
...
if (p != NULL) {
HOTSPOT_THREAD_UNPARK((uintptr_t) p);
p->unpark();
}
} UNSAFE_END
JUC背后与虚拟机交互的接口就是Unsafe.park/unpark,可以说Unsafe.park/unpark是整个JUC的基石。
Java提供了java.io.Serializable,配合 ObjectOutputStream/ObjectInputStream可以实现对象序列化和反序列化,但这是一个很慢的操作,还限制类必须提供无参的public构造方法。一些高性能第三方库会使用Unsafe类完成序列化和反序列化操作,它们调用Unsafe.getInt(Object o, long offset)等获取对象o所在偏移offset处的字段进行序列化。用Unsafe.allocateInstance调用对象的构造方法生成对象,再用Unsafe.putLong(Object o, long offset, long x)等将对象o所在偏移offset的字段设置为x值,以此来反序列化。这种方式被广泛用于一些第三方库,如著名开源分布式NoSQL数据库系统Cassandra。
4.1节讨论了JVM中五花八门的线程以及它们的作用。4.2节从源码角度分析线程API的实现,同时扩展分析线程API实现时涉及的其他重要模块如JavaCalls、os,并简单提及ParkEvent、Parker、OrderAccess组件。4.3节讨论了线程栈帧的实现。4.4节讨论虚拟机层的代码如何与Java层的代码交互,以此引出JNI和JavaCalls模块。4.5节讨论JDK中的Unsafe类,并给出它在虚拟机的具体实现。
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。