Java中的Unsafe

Java和C++语言的一个重要区别就是Java中咱们没法直接操做一块内存区域,不能像C++中那样能够本身申请内存和释放内存。Java中的Unsafe类为咱们提供了相似C++手动管理内存的能力。 Unsafe类,全限定名是sun.misc.Unsafe,从名字中咱们能够看出来这个类对普通程序员来讲是“危险”的,通常应用开发者不会用到这个类。程序员

Unsafe类是"final"的,不容许继承。且构造函数是private的:数组

public final class Unsafe {
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;

    private static native void registerNatives();
    // 构造函数是private的,不容许外部实例化
    private Unsafe() {
    }
    ...
}
复制代码

所以咱们没法在外部对Unsafe进行实例化。bash

获取Unsafe

Unsafe没法实例化,那么怎么获取Unsafe呢?答案就是经过反射来获取Unsafe:函数

public Unsafe getUnsafe() throws IllegalAccessException {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);
    return unsafe;
}
复制代码

主要功能

Unsafe的功能以下图:ui

Unsafe-xmind

普通读写

经过Unsafe能够读写一个类的属性,即便这个属性是私有的,也能够对这个属性进行读写。spa

读写一个Object属性的相关方法线程

public native int getInt(Object var1, long var2);

public native void putInt(Object var1, long var2, int var4);
复制代码

getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。其余的primitive type也有对应的方法。code

Unsafe还能够直接在一个地址上读写cdn

public native byte getByte(long var1);

public native void putByte(long var1, byte var3);
复制代码

getByte用于从指定内存地址处开始读取一个byte。putByte用于从指定内存地址写入一个byte。其余的primitive type也有对应的方法。对象

volatile读写

普通的读写没法保证可见性和有序性,而volatile读写就能够保证可见性和有序性。

public native int getIntVolatile(Object var1, long var2);

public native void putIntVolatile(Object var1, long var2, int var4);
复制代码

getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int。putIntVolatile方法用于在对象指定偏移地址处volatile写入一个int。

volatile读写相对普通读写是更加昂贵的,由于须要保证可见性和有序性,而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,可是保证有序性,所谓有序性,就是保证指令不会重排序。

有序写入

有序写入只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其余线程立马可见。

public native void putOrderedObject(Object var1, long var2, Object var4);

public native void putOrderedInt(Object var1, long var2, int var4);

public native void putOrderedLong(Object var1, long var2, long var4);
复制代码

直接内存操做

咱们都知道Java不能够直接对内存进行操做,对象内存的分配和回收都是由JVM帮助咱们实现的。可是Unsafe为咱们在Java中提供了直接操做内存的能力。

// 分配内存
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);
复制代码

CAS相关

JUC中大量运用了CAS操做,能够说CAS操做是JUC的基础,所以CAS操做是很是重要的。Unsafe中提供了int,long和Object的CAS操做:

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来实现乐观锁。

偏移量相关

public native long staticFieldOffset(Field var1);

public native long objectFieldOffset(Field var1);

public native Object staticFieldBase(Field var1);

public native int arrayBaseOffset(Class<?> var1);

public native int arrayIndexScale(Class<?> var1);
复制代码

staticFieldOffset方法用于获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量。objectFieldOffset方法用于获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量。staticFieldBase方法用于返回Field所在的对象。arrayBaseOffset方法用于返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量。arrayIndexScale方法用于计算数组中第一个元素所占用的内存空间。

线程调度

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中的park和unpark方法正是经过Unsafe来实现的:

// 挂起线程
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker); // 经过Unsafe的putObject方法设置阻塞阻塞当前线程的blocker
    UNSAFE.park(false, 0L); // 经过Unsafe的park方法来阻塞当前线程,注意此方法将当前线程阻塞后,当前线程就不会继续往下走了,直到其余线程unpark此线程
    setBlocker(t, null); // 清除blocker
}

// 唤醒线程
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
复制代码

monitorEnter方法和monitorExit方法用于加锁,Java中的synchronized锁就是经过这两个指令来实现的。

类加载

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 Object allocateInstance(Class<?> var1) throws InstantiationException;

public native boolean shouldBeInitialized(Class<?> var1);

public native void ensureClassInitialized(Class<?> var1);
复制代码

defineClass方法定义一个类,用于动态地建立类。 defineAnonymousClass用于动态的建立一个匿名内部类。 allocateInstance方法用于建立一个类的实例,可是不会调用这个实例的构造方法,若是这个类还未被初始化,则初始化这个类。 shouldBeInitialized方法用于判断是否须要初始化一个类。 ensureClassInitialized方法用于保证已经初始化过一个类。

内存屏障

public native void loadFence();

public native void storeFence();

public native void fullFence();
复制代码

loadFence:保证在这个屏障以前的全部读操做都已经完成。 storeFence:保证在这个屏障以前的全部写操做都已经完成。 fullFence:保证在这个屏障以前的全部读写操做都已经完成。

相关文章
相关标签/搜索