java中Unsafe类怎么用
这篇文章主要介绍了java中Unsafe类怎么用,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类,如果真是如此影响就太大了。
一、简单介绍
首先在Oracle的Jdk8无法获取到sun.misc包的源码,想看此包的源码可以直接下载openjdk。
1、预备工作
openjdk的源码我下载的是openjdk-8u40-class="lazy" data-src-b25-10_feb_2015,有需要的可以私信我,如果是我公众号粉丝,我会直接附加上这个百度云资源。在下载完成之后,然后就可以直接导入我们的eclipse了。
windows->preference->installed jres->选中jre->edit->rt.jar->source attachment->external folders->openjdk源码路径。此时就可以查看我们的Unsafe类的源码了。
2、简介说明
如果你学习了一些java并发包里面的类源码的话,对这个Unsafe类一定不陌生,整个java并发包底层实现的核心就是它,在很久之前盛传着这个类将要在jdk9移除,事实上如果移除了那么一大批框架将会消失,比如说赫赫有名的Netty框架。最终jdk9出现的时候也只是对其进行了改进和优化。不过这也再一次说明了这个类的重要地位。
为什么说它一半是天使一半是魔鬼呢?要回答这个问题,我们还是要从其特性来解释。
(1)Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,一旦能够直接操作内存,这也就意味着(1)不受jvm管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏。
(2)Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。
(3)直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。
因此,从上面三个角度来看,虽然在一定程度上提升了效率但是也带来了指针的不安全性。
下面我们深入到源码中看看,提供了什么方法直接操作内存。
二、源码分析
Unsafe中一共有82个public native修饰的方法,还有几十个基于这82个public native方法的其他方法。这些方法大体可以归结为以下几类:
(1)初始化操作
(2)操作对象属性
(3)操作数组元素
(4)内存管理
(5)内存屏障
(6)线程挂起和回复
(7)CAS机制
下面我们对这些方法尽进行一个大致的分析。
1、初始化
//1、注册native方法,是的Unsafe类可以操作C语言private static native void registerNatives();static { registerNatives(); sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");}//2、构造方法private Unsafe() {}//3、初始化方法private static final Unsafe theUnsafe = new Unsafe();//4、初始化方法实现@CallerSensitivepublic static Unsafe getUnsafe() { Class> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe;}
在这里我们看到Unsafe的初始化方法主要是通过getUnsafe方法的单例模式实现的,调用JVM本地方法registerNatives()和sun.reflect.Reflection,通过Reflection的getCallerClass判断当前调用的类是否是主类加载器(BootStrap classLoader)加载的,否则的话抛出一个SecurityException。这也证明了一个问题,那就是只有由主类加载器(BootStrap classLoader)加载的类才能调用这个类中的方法。
2、操作属性方法
(1)public native Object getObject(Object o, long offset);
通过给定的Java变量获取引用值。这里实际上是获取一个Java对象o中,获取偏移地址为offset的属性的值,此方法可以突破修饰符的抑制,也就是无视private、protected和default修饰符。类似的方法有getInt、getDouble等等。同理还有putObject方法。
(2)public native Object getObjectVolatile(Object o, long offset);
强制从主存中获取属性值。类似的方法有getIntVolatile、getDoubleVolatile等等。同理还有putObjectVolatile。
(3)public native void putOrderedObject(Object o, long offset, Object x);
设置o对象中offset偏移地址offset对应的Object型field的值为指定值x。这是一个有序或者有延迟的putObjectVolatile方法,并且不保证值的改变被其他线程立即看到。只有在field被volatile修饰并且期望被修改的时候使用才会生效。类似的方法有putOrderedInt和putOrderedLong。
(4)public native long staticFieldOffset(Field f);
返回给定的静态属性在它的类的存储分配中的位置(偏移地址)。
(5)public native long objectFieldOffset(Field f);
返回给定的非静态属性在它的类的存储分配中的位置(偏移地址)。
(6)public native Object staticFieldBase(Field f);
返回给定的静态属性的位置,配合staticFieldOffset方法使用。
3、操作数组
(1)public native int arrayBaseOffset(Class arrayClass);
返回数组类型的第一个元素的偏移地址(基础偏移地址)。
(2)public native int arrayIndexScale(Class arrayClass);
返回数组中元素与元素之间的偏移地址的增量。
这两个方法配合使用就可以定位到任何一个元素的地址。
4、内存管理
(1)public native int addressSize();
获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法。
(2)public native int pageSize();
获取本地内存的页数,此值为2的幂次方。
(3)public native long allocateMemory(long bytes);
分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址。
(4)public native long reallocateMemory(long address, long bytes);
通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)。
(5)public native void setMemory(Object o, long offset, long bytes, byte value);
将给定内存块中的所有字节设置为固定值(通常是0)。
5、线程挂起和恢复
(1)public native void unpark(Object thread);
释放被park创建的在一个线程上的阻塞。由于其不安全性,因此必须保证线程是存活的。
(2)public native void park(boolean isAbsolute, long time);
阻塞当前线程,一直等道unpark方法被调用。
6、内存屏障
(1)public native void loadFence();
在该方法之前的所有读操作,一定在load屏障之前执行完成。
(2)public native void storeFence();
在该方法之前的所有写操作,一定在store屏障之前执行完成
(3)public native void fullFence();
在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能。
7、CAS机制
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);
这个Unsafe类其实是贯穿到整个java并发包体系中的,不管是你看原子包还是lock包底部都有这样的一个类,我们需要记住的不是每一个方法,而是上面七类的标题。也就是具体有什么功能。
三、使用
说了这么久的源码在这里才介绍其使用,是因为官方并不推荐我们使用,也就是说我们无法直接new出来一个Unsafe类出来,那我们该如何使用呢?在很久之前我曾写过一篇介绍java反射机制的文章,没错就是这个反射机制,牛的不行。Unsafe就可以通过反射机制来获取。
public class UnsafeTest { public static void main(String[] args) throws Exception { //这里的theUnsafe就是我们源码中的那个theUnsafe Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); Unsafe unsafe = (Unsafe) theUnsafe.get(null); //1、创建对象实例 Author author = (Author) unsafe.allocateInstance(Author.class); //2、操作对象的属性 Field ageField = Author.class.getDeclaredField("age"); long fieldOffset = unsafe.objectFieldOffset(ageField); //3、操作数组 String[] strings = new String[]{"1", "2", "3"}; long i = unsafe.arrayBaseOffset(String[].class); //4、操作内存 long address = unsafe.allocateMemory(8L); }}
感谢你能够认真阅读完这篇文章,希望小编分享的“java中Unsafe类怎么用”这篇文章对大家有帮助,同时也希望大家多多支持编程网,关注编程网行业资讯频道,更多相关知识等着你来学习!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341