一,Unsafe使用建议
- unsafe有可能在未来几年的jdk版本中移除或者不允许java应用代码使用,这一点可能会导致使用了unsafe的应用无法运行在高版本的jdk中。
- unsafe不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是jvm级别的崩溃异常。
- unsafe提供的直接内存访问的方法中使用的内存不受jvm管理(无法被gc),需要手动管理,一旦出现疏忽可能成为内存泄漏的源头。
二,unsafe详解
unsafe类中一共有82个public native 修饰的方法,还有几十个基于这个82个方法的其他方法。
1.初始化
private static native void registerNatives();
static {
registerNatives();
sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
新建一个unsafe实例命名为theunsafe,通过静态方法getUnsafe获取,获取的时候需要做权限判断,由此可见,unsafe类的设计使用了单例设计模式,构造器私有化了。unsafe类做了限制,如果是普通的调用的话,他会抛出一个权限异常,只有由引导类加载器加载的类才能使用这个类的方法。最简单的方式是通过反射获取unsafe实例。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
2.类,对象和变量相关方法
2.1 getObject
通过给定的java变量获取引用值。这里实际上是获取一个java对象o中,获取偏移地址==offset的属性的值。
此方法可以突破修饰符的限制。类似的方法还有getInt,getDouble等。
2.2 putObject
将引用值存储到给定的java变量中,这里实际上是设置一个java对象o中偏移地址为offset的属性的值为x。
此方法可以突破修饰符的限制。类似的方法还有putInt,putDouble等。
2.3 getObjectVolatile
此方法和上面的getObject功能类似,不过附加了volatile,强制从主存中获取属性值。
这个方法要求被使用的属性被volatile修饰,否则和getObject没有区别。
2.4 putObjectVolatile
此方法和上面的putObject功能类似,不过附加了volatile,也就是设置值得时候强制刷新的主存,从而保证这些变更对其他线程可见。
2.5 putOrderedObject
设置o对象中offset偏移地址对应的Object型field的值为指定值x。这是一个有序或者有延迟的putObjectVolatile方法,并且不保证值得改变立马被其他线程看到。
只有在field被volatile修饰的时候期望被修改的时候才会生效
2.6 staticFieldOffset
返回给定的静态属性在他的类的存储分配中的位置(偏移地址)。这个方法仅仅针对静态属性,非静态属性调用会抛出异常。
2.7 objectFieldOffset
返回给定的非静态属性在他的类的存储分配中的位置(偏移地址)。这个方法仅仅针对非静态属性,静态属性调用会抛出异常。
2.8 staticFieldBase
返回给定的静态属性的位置,配合staticFieldOffset使用。实际上这个方法的返回值就是静态属性所在的Class对象的一个内存快照。
2.9 shouldBeInitialized
检测给定的类是否需要初始化。通常需要使用在获取一个类的静态属性的时候(一个类如果没有初始化,他的静态属性也不会初始化)。
2.10 arrayIndexScale
返回数组类型的比例因子(其实就是数据中元素偏移量地址的增量,因为数组中的元素地址是连续的)。
2.11 arrayBaseOffset
返回数组类型的第一个元素的偏移地址(基础偏移地址)。如果arrayIndexScale方法返回的比例因子不为0,可以通过结合基础偏移地址和比例因子访问数组的所有元素。
3.内存管理
3.1 allocateMemory
分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址。如果内存块的内容不被初始化,那么它们一般会变成内存垃圾。
生成的本机指针永远不会为零,并将对所有值类型进行对齐。可以通过freelemory方法释放内存块 或者通过reallocatelemory方法调整内存块大小。
bytes值为负数或者过大会抛出IIlegalArgumentException异常,如果系统拒绝分配内存会抛出Out( OfMemoryError异常。
3.2 reallocateMemory
通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单立为byte)。可以通过freelemory方法释放内存块,
或者通过reallocateMemory方法调整内存块大小。bytes值为负数或者过大会抛出IllegalArgumentEx ception异常,如果系统拒绝分配内存会抛出OutOfMemoryError异常。
3.3 freeMemory
释放通过allocatelemory方法申请的内存
3.4 setMemory
将给定内存块中的所有字节设置为固定值(通常是0)。内存块的地址由对象引用o和偏移地址共同决定,如果对象引用o为null,offset就是绝对地址。
第三个参数就是内存块的大小,如果使用allocatelenory进行内存开辟的话,这里的值应该和all ocatelemory的参数一致。value就是设置的固定值,一般为0。
4.多线程同步
4.1 monitorEnter
锁定对象,必须通过nonitorExit方法才能解锁。此方法经过实验是可以重入的,也就是可以多次调用,然后通过多次调用monitorExit进行解锁。
4.2 monitorExit
解锁对象,前提是对象必须已经调用monitorEnter进行加锁,否则抛出IIlegalMonitorStateException异常。
4.3 tryMonitorEnter
尝试锁定对象,如果加锁成功返回true,否则返回false。必须通过monitorExit方法才能解锁。
4.4 compareAndSwapObject
针对Object对象进行CAS操作。即是对应Java变量引用o,原子性地更新o中偏移地址为offset的属性的值为x,当且仅当偏移地址为offset的属性的当前值为expected才会更新成功返回true,否则返回false。
o:目标Java变量引用。
offset:目标Java变量中的目标属性的偏移地址。
expected:目标Java变量中的目标属性的期望的当前值。 x:目标Java变量中的目标属性的目标更新值。
类似的方法有compareAndSwapInt和compareAndSwapLong,在Jdk8中基于CAS扩展出来的方法有getAn daddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSet0bject,它们的作用都是:通过CAS设置新的值,返回旧的值。
5.线程的挂起和恢复
5.1 unpark
恢复线程,必须制定要恢复的线程thread。
5.2 park
阻塞当前线程直到一个unpark方法出现(被调用)。
参数:isAbsolute true 表示绝对时间,阻塞时间按照纳秒计算。false表示相对时间,阻塞时间按照毫秒计算。
简单理解isAbsolute true时精度更高。
参数:time表示阻塞时间。值为0时表示无限期阻塞,直到有一个线程通过unsafeunpark(当前阻塞线程)才会唤醒。