前言

简直每个应用 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