java中Unsafe类学习

    技术2025-11-10  24

    代码出现乱序请双击代码部分

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

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

     

    1 2 3 4 5 6 7 8 9 10 11 public final class Unsafe { private static native void registerNatives(); static { registerNatives(); sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); } private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe();

    ...

    }

    那外部怎么实例化呢,通过反射

     

    1 2 3 4 5 6 7 @CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }

     或者

     

    1 2 3 Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);

    Unsafe主要提供以下功能:

    1、通过Unsafe类可以分配内存,可以释放内存;

    类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与C语言中的3个方法对应。

    1 2 3 public native long allocateMemory(long l); public native long reallocateMemory(long l, long l1); public native void freeMemory(long l);

    2、可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的

    字段的定位:

     

    1 2 3 4 5 6 7 8 9 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);

    JAVA中对象的字段的定位可能通过staticFieldOffset方法实现,该方法返回给定field的内存地址偏移量,这个值对于给定的filed是唯一的且是固定不变的。

     

    1 public native long staticFieldOffset(Field f);

    getIntVolatile方法获取对象中offset偏移地址对应的整型field的值,支持volatile load语义。

    getLong方法获取对象中offset偏移地址对应的long型field的值

    数组元素定位:

    Unsafe类中有很多以BASE_OFFSET结尾的常量,比如ARRAY_INT_BASE_OFFSET,ARRAY_BYTE_BASE_OFFSET等,这些常量值是通过arrayBaseOffset方法得到的。arrayBaseOffset方法是一个本地方法,可以获取数组第一个元素的偏移地址。Unsafe类中还有很多以INDEX_SCALE结尾的常量,比如 ARRAY_INT_INDEX_SCALE , ARRAY_BYTE_INDEX_SCALE等,这些常量值是通过arrayIndexScale方法得到的。arrayIndexScale方法也是一个本地方法,可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置。

     

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public final class Unsafe { public static final int ARRAY_INT_BASE_OFFSET; public static final int ARRAY_INT_INDEX_SCALE; public native long staticFieldOffset(Field field); public native int getIntVolatile(Object obj, long l); public native long getLong(Object obj, long l); public native int arrayBaseOffset(Class class1); public native int arrayIndexScale(Class class1); static { ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset([I); ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale([I); } }

     

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

     

    1 2 3 public native int getInt(Object var1, long var2); public native void putInt(Object var1, long var2, int var4);

     

    getInt用于从对象的指定偏移地址处读取一个int。putInt用于在对象指定偏移地址处写入一个int。

    其他的primitive type也有对应的方法。

    Unsafe还可以直接在一个地址上读写

     

    1 2 3 public native byte getByte(long var1); public native void putByte(long var1, byte var3);

    getByte用于从指定内存地址处开始读取一个byte。putByte用于从指定内存地址写入一个byte。

    其他的primitive type也有对应的方法。

    volatile读写  (volatile的作用:1.保证可见性 2.避免指令重排序(保证有序性))

    普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。

    1 2 3 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写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。

    有序写入

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

     

    1 2 3 4 5 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);

    3、挂起与恢复

    将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

     

    1 2 3 4 5 6 7 8 9 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);

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

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class LockSupport { public static void unpark(Thread thread) { if (thread != null) unsafe.unpark(thread); } public static void park(Object blocker) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(false, 0L); setBlocker(t, null); } public static void parkNanos(Object blocker, long nanos) { if (nanos > 0) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(false, nanos); setBlocker(t, null); } } public static void parkUntil(Object blocker, long deadline) { Thread t = Thread.currentThread(); setBlocker(t, blocker); unsafe.park(true, deadline); setBlocker(t, null); } public static void park() { unsafe.park(false, 0L); } public static void parkNanos(long nanos) { if (nanos > 0) unsafe.park(false, nanos); } public static void parkUntil(long deadline) { unsafe.park(true, deadline); } }

    4、CAS操作

    是通过compareAndSwapXXX方法实现的

     

    1 2 3 4 5 6 7 8 9 10 /** * 比较obj的offset处内存位置中的值和期望的值,如果相同则更新。此更新是不可中断的。 * * @param obj 需要更新的对象 * @param offset obj中整型field的偏移量 * @param expect 希望field中存在的值 * @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值 * @return 如果field的值被更改返回true */ public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

    JUC中大量运用了CAS操作,可以说CAS操作是JUC的基础,因此CAS操作是非常重要的。Unsafe中提供了int,long和Object的CAS操作:

     

    1 2 3 4 5 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);

     5、类加载

     

    1 2 3 4 5 6 7 8 9 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方法用于保证已经初始化过一个类。

     6、内存屏障

     

    1 2 3 4 5 public native void loadFence(); public native void storeFence(); public native void fullFence();

    loadFence:保证在这个屏障之前的所有读操作都已经完成。 storeFence:保证在这个屏障之前的所有写操作都已经完成。 fullFence:保证在这个屏障之前的所有读写操作都已经完成。

     

    Processed: 0.013, SQL: 9