sun公司提供了可以用于直接操作内存的类,这个类就是sun.misc.Unsafe。因为Java本身是不会涉及到直接操作内存的,Java API也没有提供这些操作,内存管理全部交给虚拟机来做。Sun之所以提供这个类,因为有些功能现有的Java API满足不了,如果没有这个类,可能就没有现在原子类,J.U.C包了,也许也没了各种Concurrent Collection类,可能也没了NIO的堆外内存,所以这个类十分的有用,并且很重要,Sun也没有开放这个类的源代码,并且对它的使用也做了一些限制。
通过反编译看到这个类中,几乎所有的方法都是native修饰的,即使其他非native修饰的,最后也是在调用本类的其他native方法。
Unsafe提供了一个static修饰的静态方法,用来获取这个类的实例,这是一个单例。
@CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }这个getUnsafe()方法并不是直接返回Unsafe的实例,而是做了校验。通过校验直接使用getUnsafe这个方法的类的类加载器是否为Bootstrap类加载器,来做安全检查。由于Java API中的类都是由Bootstrap类加载器加载的,所以是可以直接调用这个方法来获取Unsafe的实例。不过如果要在自己的代码中使用Unsafe,则需要通过反射修改theUnsafe字段的访问修饰符,然后获取Unsafe的实例。由于Unsafe可以直接操作内存,所以存在很大的风险,使用时需要特别谨慎!
想阅读下关于原子类和J.U.C包下的一些实现,发现这些经常使用的方法,记录下每个方法的具体含义,不然没法理解整个客户端方法的行为。
整个Unsafe类中共分为以下几类操作:
底层内存信息相关的 比如内存页大小
操作对象及其字段 如:实例化对象,获取实例域的偏移量
操作类以及静态字段 如:定义一个类,获取静态字段的偏移量
操作数据对象
同步操作 提供操作监控器和CAS的支持
内存操作 主要是内存的分配,复制,销毁等 NIO中用到的多
JUC和原子类以及并发集合中主要用到以下这些方法 Unsafe.putObject()将一个引用类型的值存入到给定对象的变量中,这个方法的参数列表为putObject(Object o, long offset, Object x),这里就是将对象x的引用,存到对象o的偏移量为offset的变量上。要注意这个操作不保证内存可见性,也就是说对对象o的指定字段的更新,并不会在多线程环境下被其他线程发现值的变动。所以提供了一个具有volatile语义的方法putObjectVolatile(),这个操作具有volatile语义的store语义,并不具备load的语义,如果想在获取时具备load的语义,可以使用getObjectVolatile()。
Unsafe.park()此方法主要用于阻塞当前线程,方法的参数为park(boolean absolute, long time),如果absolute为false,time为0,则表示一直阻塞,直至unpark方法被调用,或者被中断。如果absolute为false,time不为0,则表示为给定的纳秒过后,中断阻塞,相应的线程可以被系统调度。如果absolute为true,time的单位为毫秒,不过这是一个绝对时间,Epoch Time—— Unix纪元时间 1970.1.1 零时,absolute表示的绝对是指基于这个时间的绝对时间,也就是说park(true, System.currentTimeMillis()+N)这种写法才是对的,不然输入的任意数字都没有作用的,不会起到阻塞线程的效果。
会将线程一直阻塞,直至以下情况发生:
当相应的unpark方法在park方法调用前被调用,park方法调用会被立即返回
当相应的unpark方法在park方法后被调用,park方法返回
在调用park方法的前后,如果检测到线程已经被设置为中断,则park方法立即返回
absolute为false并且time不为0,所给的纳秒已经过了
absolute为true,并且所给的时间(必须是在Epoch纪元时间的基础上,通常取当前距离纪元时间的毫秒数加上希望阻塞时间毫秒数)毫秒已经用完
无理由返回
Unsafe.unpark()将指定的线程从park阻塞状态中恢复过来,方法仅有一个参数unpark(Thraed thread),要确保thread对象没有被销毁,也就是要检查不为null
Unsafe.getObjectVolatile()获取所给对象的所给变量的值,使用volatile语义的load语义,会在实际获取这个值的时候从主存中加载,不会使用CPU缓存中的,总能确保获取到的是有效的值。 getObjectVolatile(Object o, long offset)
Unsafe.getInt()有三个重载方法getInt(Object o, long offset)、getInt(long address) 和getIntVolatile(long address),都是从指定的位置获取变量的值,只不过第一个的offset是相对于对象o的相对偏移量,第二个address是绝对地址偏移量。如果第一个方法中o为null是,offset也会被作为绝对偏移量。第三个则是带有volatile语义的load读操作。
Unsafe.putInt()同样有三个在用的重载方法,参数也差不多同putInt类似,含义则是相反,用于对指定内存地址的变量赋值。
putInt(Object o, long offset, int x)、putInt(long address) 和putIntVolatile(Object o, long offset, int x),最后一个是Volatile版本的putInt方法。
Unsafe.objectFieldOffset()