共计 5433 个字符,预计需要花费 14 分钟才能阅读完成。
前言
简直每个应用 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() 来获取。
private Unsafe() {}
@CallerSensitive
public static Unsafe getUnsafe() {Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException("Unsafe");
} else {return theUnsafe;}
}
在 getUnsafe 办法中对单例模式中的对象创立做了限度,如果是一般的调用会抛出一个 SecurityException 异样。只有由主类加载器加载的类能力调用这个办法。
那么,如何取得 Unsafe 类的对象呢?通常采纳反射机制:
public static Unsafe getUnsafe() throws IllegalAccessException {Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
当取得 Unsafe 对象之后,就能够”随心所欲“了。上面就来看看,通过 Unsafe 办法,咱们能够做些什么。
Unsafe 的次要性能
可先从依据下图从整体上理解一下 Unsafe 提供的性能:
上面筛选重要的性能进行解说。
一、内存治理
Unsafe 的内存治理性能次要包含:一般读写、volatile 读写、有序写入、间接操作内存等分配内存与开释内存的性能。
一般读写
Unsafe 能够读写一个类的属性,即使这个属性是公有的,也能够对这个属性进行读写。
// 获取内存地址指向的整数
public native int getInt(Object var1, long var2);
// 将整数写入指定内存地址
public native void putInt(Object var1, long var2, int var4);
getInt 用于从对象的指定偏移地址处读取一个 int。putInt 用于在对象指定偏移地址处写入一个 int。其余原始类型也提供有对应的办法。
另外,Unsafe 的 getByte、putByte 办法提供了间接在一个地址上进行读写的性能。
volatile 读写
一般的读写无奈保障可见性和有序性,而 volatile 读写就能够保障可见性和有序性。
// 获取内存地址指向的整数,并反对 volatile 语义
public native int getIntVolatile(Object var1, long var2);
// 将整数写入指定内存地址,并反对 volatile 语义
public native void putIntVolatile(Object var1, long var2, int var4);
volatile 读写要保障可见性和有序性,绝对一般读写更加低廉。
有序写入
有序写入只保障写入的有序性,不保障可见性,就是说一个线程的写入不保障其余线程立马可见。
// 将整数写入指定内存地址、有序或者有提早的办法
public native void putOrderedInt(Object var1, long var2, int var4);
而与 volatile 写入相比 putOrderedXX 写入代价绝对较低,putOrderedXX 写入不保障可见性,然而保障有序性,所谓有序性,就是保障指令不会重排序。
间接操作内存
Unsafe 提供了间接操作内存的能力:
// 分配内存
public native long allocateMemory(long var1);
// 从新分配内存
public native long reallocateMemory(long var1, long var3);
// 内存初始化
public native void setMemory(long var1, long var3, byte var5);
// 内存复制
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 革除内存
public native void freeMemory(long var1);
对应操作内存,也提供了一些获取内存信息的办法:
// 获取内存地址
public native long getAddress(long var1);
public native int addressSize();
public native int pageSize();
值得注意的是:利用 copyMemory 办法能够实现一个通用的对象拷贝办法,无需再对每一个对象都实现 clone 办法,但只能做到对象浅拷贝。
二、非常规对象实例化
通常,咱们通过 new 或反射来实例化对象,而 Unsafe 类提供的 allocateInstance 办法,能够间接生成对象实例,且无需调用构造方法和其余初始化办法。
这在对象反序列化的时候会很有用,可能重建和设置 final 字段,而不须要调用构造方法。
// 间接生成对象实例,不会调用这个实例的构造方法
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
三、类加载
通过以下办法,能够实现类的定义、创立等操作。
// 办法定义一个类,用于动静地创立类
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
// 动静的创立一个匿名外部类
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
// 判断是否须要初始化一个类
public native boolean shouldBeInitialized(Class<?> var1);
// 保障曾经初始化过一个类
public native void ensureClassInitialized(Class<?> var1);
四、偏移量相干
Unsafe 提供以下办法获取对象的指针,通过对指针进行偏移,不仅能够间接批改指针指向的数据(即便它们是公有的),甚至能够找到 JVM 曾经认定为垃圾、能够进行回收的对象。
// 获取动态属性 Field 在对象中的偏移量,读写动态属性时必须获取其偏移量
public native long staticFieldOffset(Field var1);
// 获取非动态属性 Field 在对象实例中的偏移量,读写对象的非动态属性时会用到这个偏移量
public native long objectFieldOffset(Field var1);
// 返回 Field 所在的对象
public native Object staticFieldBase(Field var1);
// 返回数组中第一个元素理论地址绝对整个数组对象的地址的偏移量
public native int arrayBaseOffset(Class<?> var1);
// 计算数组中第一个元素所占用的内存空间
public native int arrayIndexScale(Class<?> var1);
五、数组操作
数组操作提供了以下办法:
// 获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> var1);
// 获取数组中元素的增量地址
public native int arrayIndexScale(Class<?> var1);
arrayBaseOffset 与 arrayIndexScale 配合起来应用,就能够定位数组中每个元素在内存中的地位。
因为 Java 的数组最大值为 Integer.MAX_VALUE,应用 Unsafe 类的内存调配办法能够实现超大数组。实际上这样的数据就能够认为是 C 数组,因而须要留神在适合的工夫开释内存。
六、线程调度
线程调度相干办法如下:
// 唤醒线程
public native void unpark(Object var1);
// 挂起线程
public native void park(boolean var1, long var2);
// 用于加锁,已废除
public native void monitorEnter(Object var1);
// 用于加锁,已废除
public native void monitorExit(Object var1);
// 用于加锁,已废除
public native boolean tryMonitorEnter(Object var1);
通过 park 办法将线程进行挂起,线程将始终阻塞到超时或中断条件呈现。unpark 办法能够终止一个挂起的线程,使其恢复正常。
整个并发框架中对线程的挂起操作被封装在 LockSupport 类中,LockSupport 类中有各种版本 pack 办法,但最终都调用了 Unsafe.park() 办法。
七、CAS 操作
Unsafe 类的 CAS 操作可能是应用最多的办法。它为 Java 的锁机制提供了一种新的解决办法,比方 AtomicInteger 等类都是通过该办法来实现的。compareAndSwap 办法是原子的,能够防止沉重的锁机制,进步代码效率。
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
CAS 个别用于乐观锁,它在 Java 中有宽泛的利用,ConcurrentHashMap,ConcurrentLinkedQueue 中都有用到 CAS 来实现乐观锁。
八、内存屏障
JDK8 新引入了用于定义内存屏障、防止代码重排的办法:
// 保障在这个屏障之前的所有读操作都曾经实现
public native void loadFence();
// 保障在这个屏障之前的所有写操作都曾经实现
public native void storeFence();
// 保障在这个屏障之前的所有读写操作都曾经实现
public native void fullFence();
九、其余
当然,Unsafe 类中还提供了大量其余的办法,比方下面提到的 CAS 操作,以 AtomicInteger 为例,当咱们调用 getAndIncrement、getAndDecrement 等办法时,实质上调用的就是 Unsafe 的 getAndAddInt 办法。
public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndDecrement() {return unsafe.getAndAddInt(this, valueOffset, -1);
}
在实际的过程中,如果浏览其余框架或类库实现,当发现用到 Unsafe 类,可对照该类的整体性能,联合利用场景进行剖析,即可大略理解其性能。
小结
通过本文的剖析,想必大家在浏览源码时,再遇到 Unsafe 类的调用,肯定大略猜出它是用来干什么的。应用 Unsafe 类的次要目标大多数状况下是为了晋升运行效率、加强性能。但同时也面临着出错、内存治理等危险。只有深刻理解,且有必要的状况下才倡议应用。
博主简介:《SpringBoot 技术底细》技术图书作者,热爱钻研技术,写技术干货文章。
公众号:「程序新视界」,博主的公众号,欢送关注~
技术交换:请分割博主微信号:zhuan2quan