🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
java是一个安全性很高的平台,开发者在使用过程中,并不需要直接对内存进行操作,Unsafe对象字如其名,是一个不安全的工具类,可以突破java语言的限制,对内存进行操作。 ## 初始化 Unsafe的构造方法是私有的,我们不能直接通过new来获取实例,Unsafe内部有一个静态方法getUnsafe可以获取实例; ``` private Unsafe() {} public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null) //没有被任何类加载器加载时是null。它会抛出SecurityException 异常 throw new SecurityException("Unsafe"); return theUnsafe; } ``` getUnsafe()方法获取实例,直接调用时会抛出异常java.lang.SecurityException: Unsafe,只有java认为是安全的代码才可以加载并获取Unsafe实例。如此一来,我们编写的代码就不能直接通过该方法获取Unsafe实例了。 不过Unsafe的内部有一个theUnsafe变量,我们可以通过反射来获取这个实例。获取实例的方法很简单,获取theUnsafe属性,在调用get方法即可 ``` private static final Unsafe theUnsafe = new Unsafe(); // 通过反射获取Unsafe实例 public static Unsafe getUnsafe() throws Exception{ Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); return unsafe; } ``` ## 相关操作 ### 1.分配内存 allocateMemory方法可以用来分配本地内存,下面模拟一个数组UnsafeArray,内部保存byte类型数据;初始化时,根据数组大小分配内存,返回address为数组的基地址,set和get时根据偏移量调用putByte和getByte修改数组内数据。 ``` int BYTESIZE = 1; public UnsafeArray(long size) { address = UNSAFE.allocateMemory(size *BYTESIZE); } public byte get(long index) { return UNSAFE.getByte(address + (index * BYTESIZE)); } public void set(long index,byte result) { UNSAFE.putByte(address + (index * BYTESIZE), result); } ``` ### 2.对象属性 objectFieldOffset方法可以类的某个属性在Class中的偏移,根据偏移量调用getInt()和UNFASE.getAndSetInt()可以获取/设置对象实例该属性的值。下面以SampleClass类为例,内部有一个整型i默认为5,long型默认为10 ``` public final class SampleClass{ private int i = 5; private long l = 10; private byte[] buf = new byte[4]; } ``` 下面是具体的使用过程 ``` public static void testObject() throws NoSuchFieldException, SecurityException { // 由于对象头是8+8 int的offset应该是16 long offseti = UNFASE.objectFieldOffset(SampleClass.class.getDeclaredField("i")); System.out.println("offset of i is " + offseti);//offset of i is 12 long offsetl = UNFASE.objectFieldOffset(SampleClass.class.getDeclaredField("l")); System.out.println("offset of l is " + offsetl);//offset of l is 16 SampleClass sampleClass = new SampleClass(); System.out.println("testObject offset of offseti : "+ UNFASE.getInt(sampleClass, offseti));// testObject offset of offseti : 5 System.out.println("testObject offset of offsetl : "+ UNFASE.getInt(sampleClass, offsetl));// testObject offset of offsetl : 10 // UNFASE.getAndSetInt(sampleClass, offseti, 99); System.out.println("sampleClass.getI() = " + sampleClass.getI());// sampleClass.getI() = 99 UNFASE.getAndSetLong(sampleClass, offsetl, 88); System.out.println("sampleClass.getL() = " + sampleClass.getL());// sampleClass.getL() = 88 } ``` ## 3.CAS CAS是java并发包中最常见的无锁方法,底层均由unsafe实现,有如下方法: ``` /** * 如果对象offset的当前值是expected则更新为x */ public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x); ``` 在AtomicInteger的源码中,可以看到cas的使用,其内部使用value记录了实际的int值,初始化时获取了value在AtomicInteger的偏移valueOffset,在AtomicInteger提供的方法中,会使用unsafe的相关方法完成cas操作。 ``` //AtomicInteger类源码 public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } ``` ## 4.park unsafe有两个方法park()和unpark()可以用来停止线程和恢复线程,并发包中的很多锁机制都使用了该方法,下面的例子中会启动一个线程,休眠5秒或被唤醒后结束;main线程休眠2秒后unpark恢复了内部线程。 ``` Thread thread = new Thread(new Runnable() { public void run() { UNFASE.park(false, TimeUnit.SECONDS.toNanos(5)); System.out.println("innrt thread end"); } }); thread.start(); System.out.println("begin thread!"); TimeUnit.SECONDS.sleep(2); UNFASE.unpark(thread); System.out.println("main thread unpark!"); ``` 打印内容为: ``` 0s-begin thread! 2s-main thread unpark! 2s-innrt thread end ``` ## 5.monitor 临界区相关的方法有 ``` public native void monitorEnter(Object o); public native void monitorExit(Object o); public native boolean tryMonitorEnter(Object o); ``` 使用方法类似与synchronized类似,进入临界区之前需要获得锁,跳出临界区后要释放锁。park中的例子可以改写为下面的方式: ``` final Object lock = new Object(); Thread thread = new Thread(new Runnable() { public void run() { UNFASE.monitorEnter(lock); System.out.println("innrt thread end"); } }); thread.start(); UNFASE.monitorEnter(lock); System.out.println("begin thread!"); TimeUnit.SECONDS.sleep(2); UNFASE.monitorExit(lock); System.out.println("main thread unpark!"); ``` # netty对unsafe的使用 netty中在内存相关的操作和cas中使用了unsafe对象,具体程序在PlatformDependent0中。 1.获取DirectByteBuffer的地址 由于unsafe主要用于分配直接内存,首先获取了nio的Buffer中是否有address属性,如果内有则不需要获取Unsafe对象实例了。 ``` inal ByteBuffer direct = ByteBuffer.allocateDirect(1); ...... final Field field = Buffer.class.getDeclaredField("address"); field.setAccessible(true); // if direct really is a direct buffer, address will be non-zero if (field.getLong(direct) == 0) { return null; } return field; ``` 2. 获取Unsafe实例,使用反射的方法获取theUnsafe属性 3. 大量使用了内存相关的方法