
各大框架都在使用的Unsafe类,到底有多神奇?
前言
几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了sun.misc.Unsafe,比如Netty、Cassandra、Hadoop、Kafka等。
Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下,不属于Java标准。
很早之前,在阅读并发编程相关类的源码时,看到Unsafe类,产生了一个疑惑:既然是并发编程中用到的类,为什么命名为Unsafe呢?
深入了解之后才知道,这里的Unsafe并不是说线程安全与否,而是指:该类对于普通的程序员来说是”危险“的,一般应用开发者不会也不应该用到此类。
因为Unsafe类功能过于强大,提供了一些可以绕开JVM的更底层功能。它让Java拥有了像C语言的指针一样操作内存空间的能力,能够提升效率,但也带来了指针的问题。官方并不建议使用,也没提供文档支持,甚至计划在高版本中去掉该类。
但对于开发者来说,了解该类提供的功能更有助于我们学习CAS、并发编程等相关的知识,还是非常有必要学习和了解的。
Unsafe的构造
Unsafe类是"final"的,不允许继承,且构造函数是private,使用了单例模式来通过一个静态方法getUnsafe()来获取。
在getUnsafe方法中对单例模式中的对象创建做了限制,如果是普通的调用会抛出一个SecurityException异常。只有由主类加载器加载的类才能调用这个方法。
那么,如何获得Unsafe类的对象呢?通常采用反射机制:
当获得Unsafe对象之后,就可以”为所欲为“了。下面就来看看,通过Unsafe方法,我们可以做些什么。
Unsafe的主要功能
可先从根据下图从整体上了解一下Unsafe提供的功能:
Unsafe功能概述
下面挑选重要的功能进行讲解。
一、内存管理
Unsafe的内存管理功能主要包括:普通读写、volatile读写、有序写入、直接操作内存等分配内存与释放内存的功能。
普通读写
Unsafe可以读写一个类的属性,即便这个属性是私有的,也可以对这个属性进行读写。
getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。其他原始类型也提供有对应的方法。
另外,Unsafe的getByte、putByte方法提供了直接在一个地址上进行读写的功能。
volatile读写
普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。
volatile读写要保证可见性和有序性,相对普通读写更加昂贵。
有序写入
有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。
而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。
直接操作内存
Unsafe提供了直接操作内存的能力:
对应操作内存,也提供了一些获取内存信息的方法:
值得注意的是:利用copyMemory方法可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,但只能做到对象浅拷贝。
二、非常规对象实例化
通常,我们通过new或反射来实例化对象,而Unsafe类提供的allocateInstance方法,可以直接生成对象实例,且无需调用构造方法和其他初始化方法。
这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。
三、类加载
通过以下方法,可以实现类的定义、创建等操作。
四、偏移量相关
Unsafe提供以下方法获取对象的指针,通过对指针进行偏移,不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。
五、数组操作
数组操作提供了以下方法:
arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。
由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。
六、线程调度
线程调度相关方法如下:
通过park方法将线程进行挂起, 线程将一直阻塞到超时或中断条件出现。unpark方法可以终止一个挂起的线程,使其恢复正常。
整个并发框架中对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。
七、CAS操作
Unsafe类的CAS操作可能是使用最多的方法。它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。
CAS一般用于乐观锁,它在Java中有广泛的应用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS来实现乐观锁。
八、内存屏障
JDK8新引入了用于定义内存屏障、避免代码重排的方法:
九、其他
当然,Unsafe类中还提供了大量其他的方法,比如上面提到的CAS操作,以AtomicInteger为例,当我们调用getAndIncrement、getAndDecrement等方法时,本质上调用的就是Unsafe的getAndAddInt方法。
在实践的过程中,如果阅读其他框架或类库实现,当发现用到Unsafe类,可对照该类的整体功能,结合应用场景进行分析,即可大概了解其功能。
小结
经过本文的分析,想必大家在阅读源码时,再遇到Unsafe类的调用,一定大概猜出它是用来干什么的。使用Unsafe类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解,且有必要的情况下才建议使用。
文章转载自公众号:程序员新视界
