本文转载自:https://www.cnblogs.com/pkufork/p/java_unsafe.htmlhtml
最近在看Java并发包的源码,发现了神奇的Unsafe类,仔细研究了一下,在这里跟你们分享一下。java
Unsafe类是在sun.misc包下,不属于Java标准。可是不少Java的基础类库,包括一些被普遍使用的高性能开发库都是基于Unsafe类开发的,好比Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提高Java运行效率,加强Java语言底层操做能力方面起了很大的做用。数组
Unsafe类使Java拥有了像C语言的指针同样操做内存空间的能力,同时也带来了指针的问题。过分的使用Unsafe类会使得出错的概率变大,所以Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,若是真是如此影响就太大了。多线程
一般咱们最好也不要使用Unsafe类,除非有明确的目的,而且也要对它有深刻的了解才行。要想使用Unsafe类须要用一些比较tricky的办法。Unsafe类使用了单例模式,须要经过一个静态方法getUnsafe()来获取。但Unsafe类作了限制,若是是普通的调用的话,它会抛出一个SecurityException异常;只有由主类加载器加载的类才能调用这个方法。其源码以下:并发
1 public static Unsafe getUnsafe() { 2 Class var0 = Reflection.getCallerClass(); 3 if(!VM.isSystemDomainLoader(var0.getClassLoader())) { 4 throw new SecurityException("Unsafe"); 5 } else { 6 return theUnsafe; 7 } 8 }
网上也有一些办法来用主类加载器加载用户代码,好比设置bootclasspath参数。但更简单方法是利用Java反射,方法以下:框架
1 Field f = Unsafe.class.getDeclaredField("theUnsafe"); 2 f.setAccessible(true); 3 Unsafe unsafe = (Unsafe) f.get(null);
获取到Unsafe实例以后,咱们就能够随心所欲了。Unsafe类提供了如下这些功能:oop
1、内存管理。包括分配内存、释放内存等。性能
该部分包括了allocateMemory(分配内存)、reallocateMemory(从新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各类基本类型的操做。spa
利用copyMemory方法,咱们能够实现一个通用的对象拷贝方法,无需再对每个对象都实现clone方法,固然这通用的方法只能作到对象浅拷贝。线程
2、很是规的对象实例化。
allocateInstance()方法提供了另外一种建立实例的途径。一般咱们能够用new或者反射来实例化对象,使用allocateInstance()方法能够直接生成对象实例,且无需调用构造方法和其它初始化方法。
这在对象反序列化的时候会颇有用,可以重建和设置final字段,而不须要调用构造方法。
3、操做类、对象、变量。
这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。
经过这些方法咱们能够获取对象的指针,经过对指针进行偏移,咱们不只能够直接修改指针指向的数据(即便它们是私有的),甚至能够找到JVM已经认定为垃圾、能够进行回收的对象。
4、数组操做。
这部分包括了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就能够定位数组中每一个元素在内存中的位置。
因为Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法能够实现超大数组。实际上这样的数据就能够认为是C数组,所以须要注意在合适的时间释放内存。
5、多线程同步。包括锁机制、CAS操做等。
这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。
其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。
Unsafe类的CAS操做多是用的最多的,它为Java的锁机制提供了一种新的解决办法,好比AtomicInteger等类都是经过该方法来实现的。compareAndSwap方法是原子的,能够避免繁重的锁机制,提升代码效率。这是一种乐观锁,一般认为在大部分状况下不出现竞态条件,若是操做失败,会不断重试直到成功。
6、挂起与恢复。
这部分包括了park、unpark等方法。
将一个线程进行挂起是经过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark能够终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操做被封装在 LockSupport类中,LockSupport类中有各类版本pack方法,但最终都调用了Unsafe.park()方法。
7、内存屏障。
这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的,用于定义内存屏障,避免代码重排序。
loadFence() 表示该方法以前的全部load操做在内存屏障以前完成。同理storeFence()表示该方法以前的全部store操做在内存屏障以前完成。fullFence()表示该方法以前的全部load、store操做在内存屏障以前完成。