也许我们会惊讶java中有一个类被命名为“unsafe”。如果你慢慢看,你会发现这个类的神奇之处。虽然很厉害,但是也不是那么安全。一、简介首先,sun.misc包的源码在Oracle的Jdk8中无法获取。如果想看这个包的源码,可以直接下载openjdk。一、准备工作我下载的openjdk源码是openjdk-8u40-src-b25-10_feb_2015。有需要的可以私信我。如果你是我的公众号粉丝,我就直接附上这个百度云资源。下载完成后,就可以直接导入到我们的eclipse中了。windows->preference->installedjres->selectjre->edit->rt.jar->sourceattachment->externalfolders->openjdksourcepath。此时可以查看我们的Unsafe类的源码。2.简介:如果你学习过javaconcurrent包中类的一些源码,你一定对这个Unsafe类不陌生。它是整个java并发包底层实现的核心。很久以前就有传言说这个类会在jdk9中被移除。事实上,如果去掉它,就会有大量的框架消失,比如大名鼎鼎的Netty框架。最后jdk9出现的时候,只是做了改进和优化。但是这也再次说明了这个类的重要性。为什么说它是一半天使一半恶魔呢?要回答这个问题,还是要从它的特点来解释。Unsafe类使Java具有像C语言中的指针一样操作内存空间的能力。一旦可以直接操作内存,就说明(1)它不是jvm管理的,也就是说不能GC,需要我们手动GC。稍有不慎就会造成内存泄漏。(2)在Unsafe的很多方法中,必须提供原始地址(内存地址)和被替换对象的地址。偏移量必须自己计算。一旦出现问题,就会引发JVM崩溃级别的异常,进而导致整个JVM实例崩溃。应用程序直接崩溃。(3)直接操作内存也意味着速度更快,在高并发的情况下可以很好的提高效率。因此,从以上三个角度来看,虽然在一定程度上提高了效率,但也带来了指针的不安全性。接下来我们深入源码,看看提供了哪些方法可以直接操作内存。2.源码分析Unsafe中共有82个公共原生修改方法,基于这82个公共原生方法还有几十个其他方法。这些方法大致可以分为以下几类:(1)初始化操作(2)操作对象属性(3)操作数组元素(4)内存管理(5)内存屏障(6)线程挂起和回收(7)CAS机制下面我们对这些方法进行粗略的分析。1.初始化//1.注册native方法,Unsafe类可以操作C语言privatestaticnativevoidregisterNatives();static{registerNatives();sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class,"getUnsafe");}//2、构造方法privateUnsafe(){}//3、初始化方法privatestaticfinalUnsafetheUnsafe=newUnsafe();//4、初始化方法实现@CallerSensitivepublicstaticUnsafegetUnsafe(){Class>caller=Reflection.getCallerClass();if(!VM.isSystemDomainLoader(caller.getClassLoader()))thrownewSecurityException("Unsafe");returntheUnsafe;}这里看到Unsafe的初始化方法主要是通过getUnsafe方法的单例模式实现的,调用JVM本地方法registerNatives()和sun.reflect.Reflection,判断是否当前调用的类由主类加载器(BootStrapclassLoader)通过Reflection的getCallerClass加载,否则抛出SecurityException。这也证明了一个问题,就是只有主类加载器(BootStrapclassLoader)加载的类才能调用这个类中的方法。2、操作属性的方法(1)publicnativeObjectgetObject(Objecto,longoffset);通过给定的Java变量获取引用值。这其实就是获取一个Java对象o中偏移地址为offset的属性的值。该方法可以突破修饰符的压制,即忽略private、protected和default修饰符。类似的方法还有getInt、getDouble等。同样,还有一个putObject方法。(2)publicnativeObjectgetObjectVolatile(Objecto,longoffset);强行从主存中获取属性值。类似的方法还有getIntVolatile、getDoubleVolatile等。以同样的方式有putObjectVolatile。(3)publicnativevoidputOrderedObject(Objecto,longoffset,Objectx);将对象o中offset偏移地址offset对应的Objecttype字段的值设置为指定值x。这是一个有序的或延迟的putObjectVolatile方法,不保证其他线程会立即看到值的更改。只有当字段被volatile修改且预期被修改时才会生效。类似的方法还有putOrderedInt和putOrderedLong。(4)publicnativelongstaticFieldOffset(Fieldf);返回给定静态属性在其类的存储分配中的位置(偏移地址)。(5)publicnativelongobjectFieldOffset(Fieldf);返回给定非静态属性在其类的存储分配中的位置(偏移地址)。(6)publicnativeObjectstaticFieldBase(Fieldf);返回给定静态属性的位置,与staticFieldOffset方法一起使用。3.数组操作(1)publicnativeintarrayBaseOffset(ClassarrayClass);返回数组类型第一个元素的偏移地址(基偏移地址)。(2)publicnativeintarrayIndexScale(ClassarrayClass);返回数组中元素之间偏移地址的增量。这两种方法可以一起使用来定位任何元素的地址。4.内存管理(1)publicnativeintaddressSize();获取局部指针的大小(单位为byte),一般为4或8。常量ADDRESS_SIZE就是调用这个方法。(2)publicnativeintpageSize();获取本地内存中的页数,为2的幂。(3)publicnativelongallocateMemory(longbytes);分配一块新的本地内存,以字节为单位指定内存块的大小(单位是字节),并返回新分配的内存地址。(4)publicnativelongreallocateMemory(longaddress,longbytes);通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小以字节为单位指定(单位为字节)。(5)publicnativevoidsetMemory(Objecto,longoffset,longbytes,bytevalue);将给定内存块中的所有字节设置为固定值(通常为0)。5、线程挂起与回收(1)publicnativevoidunpark(Objectthread);释放由park在线程上创建的块。因为它的不安全性,必须保证线程是存活的。(2)publicnativevoidpark(booleanisAbsolute,longtime);`阻塞当前线程,等待unpark方法被调用。6.内存屏障(1)publicnativevoidloadFence();此方法之前的所有读取操作都必须在加载屏障之前执行。(2)publicnativevoidstoreFence();此方法之前的所有写操作必须在storebarrier之前执行(3)publicnativevoidfullFence();该方法之前的所有读写操作都必须在fullbarrier之前执行完毕,这个memorybarrier就相当于上面两个(loadbarrier和storebarrier)的组合功能。7.CAS机制publicfinalnativebooleancompareAndSwapObject(Objecto,longoffset,Objectexpected,Objectx);publicfinalnativebooleancompareAndSwapInt(Objecto,longoffset,intexpected,intx);publicfinalnativebooleancompareAndSwapLong(Objecto,longoffset,longexpected,longx);这个Unsafe类其实是贯穿整个java并发包体系的,不管你看atomic包还是lock包底层,都有这么一个类。我们需要记住的不是每一个方法,而是以上七类的标题。也就是它有什么功能。3.使用讲了这么久的源码,这里介绍一下它的使用,因为官方不推荐我们使用,也就是说我们不能直接创建一个Unsafe类,那我们应该怎么使用呢?很久以前,我用自己写了一篇介绍java反射机制的文章。对,就是这个反射机制,不好。unsafe可以通过反射机制获取。publicclassUnsafeTest{publicstaticvoidmain(String[]args)throwsException{//这里的Unsafe是UnsafeFieldtheUnsafe=Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);Unsafeunsafe=(Unsafe)theUnsafe.get(null);//1。创建对象实例Authorauthor=(Author)unsafe.allocateInstance(Author.class);//2.操作对象的属性FieldageField=Author.class.getDeclaredField("age");longfieldOffset=unsafe。objectFieldOffset(ageField);//3.操作数组String[]strings=newString[]{"1","2","3"};longi=unsafe.arrayBaseOffset(String[].class);//4.操作内存longaddress=unsafe.allocateMemory(8L);}}这里只是一些简单的例子,其用法可以参考源码分析中的七个方向。注意:面试的时候有个小技巧。关于Java语言的特性,有很多功能是违背Java语言设计的。这个类可以用来回答这篇文章。二维码关注。转载本文请联系愚公移山公众号。
