前言几乎每一个使用Java的工具、软件基础设施、高性能开发库底层都会用到sun.misc.Unsafe,比如Netty、Cassandra、Hadoop、Kafka等,Unsafe类起到了很大的作用在提高Java运行效率,增强Java语言底层运行能力方面。但是Unsafe类在sun.misc包下,不属于Java标准。很久以前,在阅读并发编程相关类的源码时,看到Unsafe这个类,就有一个疑惑:既然是并发编程中用到的类,为什么要取名为Unsafe呢?深入了解后,才明白这里的Unsafe并不是说线程安全与否,而是指:这个类对普通程序员来说是“危险的”,一般应用开发者不会也不应该使用这个类。因为Unsafe类太强大了,它提供了一些更底层的功能,可以绕过JVM。它让Java拥有了像C语言指针一样操作内存空间的能力,可以提高效率,但是也带来了指针的问题。官方不推荐使用,也不提供文档支持,甚至打算在高版本中去掉这个类。但是对于开发者来说,了解这个类提供的功能,对于我们学习CAS、并发编程等相关知识更有帮助,学习和理解是非常有必要的。Unsafe的构造Unsafe类是“final”的,不允许继承,构造函数是私有的,使用单例模式通过静态方法getUnsafe()获取。privateUnsafe(){}@CallerSensitivepublicstaticUnsafegetUnsafe(){Classvar0=Reflection.getCallerClass();如果(!VM.isSystemDomainLoader(var0.getClassLoader())){thrownewSecurityException("Unsafe");}else{返回不安全;}}在getUnsafe方法中,限制了单例模式下的对象创建。如果是正常调用,会抛出SecurityException。只有主类加载器加载的类才能调用该方法。那么,如何获取Unsafe类的对象呢?通常使用反射机制:publicstaticUnsafegetUnsafe()throwsIllegalAccessException{FieldunsafeField=Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);return(Unsafe)unsafeField.get(null);}当Unsafe对象之后,你可以“为所欲为”。让我们看看我们可以通过Unsafe方法做什么。Unsafe的主要功能可以先根据下图整体了解Unsafe提供的功能:下面选择重要的功能进行说明。1.内存管理Unsafe的内存管理功能主要有:普通读写、volatile读写、顺序写、直接内存操作等内存分配和释放功能。普通读写Unsafe可以读写一个类的属性,即使属性是私有的,也可以读写这个属性。//获取内存地址指向的整数publicnativeintgetInt(Objectvar1,longvar2);//将整数写入指定内存地址publicnativevoidputInt(Objectvar1,longvar2,intvar4);getInt用于从对象中读取指定偏移地址处的一个int。putInt用于在对象的指定偏移地址写入一个int。其他原始类型也提供相应的方法。另外,Unsafe的getByte和putByte方法提供了直接读写地址的功能。volatile读写普通读写不能保证可见性和有序性,而volatile读写可以保证可见性和有序性。//获取内存地址指向的整数,支持volatile语义publicnativeintgetIntVolatile(Objectvar1,longvar2);//将整数写入指定内存地址,支持volatile语义publicnativevoidputIntVolatile(Objectvar1,longvar2,intvar4);volatile读写必须保证可见性和有序性,比普通读写的开销更大。顺序写顺序写只保证写的顺序,不保证可见性。也就是说,一个线程写入并不能保证其他线程立即可见。//将整数写入指定内存地址,有序或延迟方法publicnativevoidputOrderedInt(Objectvar1,longvar2,intvar4);与volatilewrite相比,putOrderedXX的写入成本相对较低,putOrderedXXwriteVisibility不保证,但顺序有保证。所谓顺序就是保证指令不会被重新排序。直接内存操作Unsafe提供了直接操作内存的能力://分配内存publicnativelongallocateMemory(longvar1);//重新分配内存publicnativelongreallocateMemory(longvar1,longvar3);//内存初始化publicnativevoidsetMemory(longvar1,longvar3,bytevar5);//内存复制publicnativevoidcopyMemory(Objectvar1,longvar2,Objectvar4,longvar5,longvar7);//清除内存publicnativevoidfreeMemory(longvar1);为了操作内存,它也提供了一些获取内存信息的方法://获取内存地址publicnativelonggetAddress(longvar1);publicnativeintaddressSize();publicnativeintpageSize();值得注意的是:使用copyMemory方法可以实现一个通用的对象复制方法,不需要为每个对象都实现clone方法,而只需要实现对象的浅拷贝即可。二、非常规对象实例化通常我们通过new或者反射来实例化对象,Unsafe类提供的allocateInstance方法可以直接生成对象实例,不需要调用构造函数等初始化方法。这在对象被反序列化时很有用,可以在不调用构造函数的情况下重建和设置最终字段。//直接生成一个对象实例,不会调用这个实例的构造函数。publicnativeObjectallocateInstance(Class>var1)throwsInstantiationException;3.类加载通过下面的方法,可以实现类的定义和创建等操作。//该方法定义了一个用于动态创建类的类publicnativeClass>defineClass(Stringvar1,byte[]var2,intvar3,intvar4,ClassLoadervar5,ProtectionDomainvar6);//动态创建一个匿名内部类publicnativeClass>defineAnonymousClass(Class>var1,byte[]var2,Object[]var3);//判断一个类是否需要初始化publicnativebooleanshouldBeInitialized(Class>var1);//保证初始化了一个类publicnativevoidensureClassInitialized(Class>var1);4.Offset相关Unsafe提供了以下方法来获取对象的指针。通过偏移指针,不仅可以直接修改指针指向的数据(即使是Private),甚至可以找到JVM已经识别为垃圾的对象,可以回收。//获取静态属性Field在对象中的偏移量,读写静态属性时必须获取该偏移量publicnativelongstaticFieldOffset(Fieldvar1);//获取非静态属性Field在对象实例中的偏移量,这个偏移量会在读写对象的非静态属性时用到数组第一个元素的值该地址相对于整个数组对象地址的偏移量publicnativeintarrayBaseOffset(Class>var1);//计算数组第一个元素占用的内存空间publicnativeintarrayIndexScale(Class>var1);5、数组操作数组操作提供如下方法://获取数组首元素的偏移地址publicnativeintarrayBaseOffset(Class>var1);//获取数组中元素的增量地址publicnativeintarrayIndexScale(Class>var1);arrayBaseOffset与arrayIndexScale配合使用,用于定位数组中每个元素在内存中的位置。由于Java中数组的最大值是Integer.MAX_VALUE,所以使用Unsafe类的内存分配方式可以实现非常大的数组。其实这样的数据可以认为是一个C数组,所以需要注意适时释放内存。6.线程调度线程调度相关方法如下://唤醒线程publicnativevoidunpark(Objectvar1);//挂起线程publicnativevoidpark(booleanvar1,longvar2);//用于加锁,obsoletepublicnativevoidmonitorEnter(Objectvar1);//用于锁定,obsoletepublicnativevoidmonitorExit(Objectvar1);//用于锁定,obsoletepublicnativebooleantryMonitorEnter(Objectvar1);挂起时,线程将阻塞直到超时或中断条件发生。unpark方法可以终止挂起的线程并使其恢复正常。整个并发框架中的线程挂起操作都封装在LockSupport类中。LockSupport类中的pack方法有多种版本,但最终都会调用Unsafe.park()方法。七、CAS操作不安全的CAS操作可能是使用最多的方法。它为Java的锁机制提供了一种新的解决方案,比如AtomicInteger等类都是通过该方法实现的。compareAndSwap方法是原子的,可以避免重锁机制,提高代码效率。publicfinalnativebooleancompareAndSwapObject(Objectvar1,longvar2,Objectvar4,Objectvar5);publicfinalnativebooleancompareAndSwapInt(Objectvar1,longvar2,intvar4,intvar5);publicfinalnativebooleancompareAndSwapLong(Objectvar1,longvar2,longvar4,longvar6);CAS一般用于乐观锁。它在Java中被广泛使用。ConcurrentHashMap和ConcurrentLinkedQueue使用CAS实现乐观锁。8.内存屏障JDK8引入了一种新的方法来定义内存屏障,避免代码重排://确保这个屏障之前的所有读取操作都已经完成publicnativevoidloadFence();//确保这个屏障之前的所有读取操作AllwriteoperationshavebeencompletedpublicnativevoidstoreFence();//确保这个barrier之前的所有读写操作都已经完成publicnativevoidfullFence();9.其他当然,Unsafe类还提供了大量其他的方法,比如上面提到的CAS操作,以AtomicInteger为例,当我们调用getAndIncrement、getAndDecrement等方法时,本质上就是调用了Unsafe的getAndAddInt方法。publicfinalintgetAndIncrement(){returnunsafe.getAndAddInt(this,valueOffset,1);}publicfinalintgetAndDecrement(){returnunsafe.getAndAddInt(this,valueOffset,-1);}在实践中,如果你阅读其他框架或类库实现,当发现使用了Unsafe类时,可以对比类的整体功能,结合应用场景进行分析,大致了解其功能。小结看完这篇文章,大家在阅读源码的时候遇到Unsafe类的调用,一定大概猜到它是干什么用的。大多数情况下,使用Unsafe类的主要目的是为了提高运行效率和增强功能。但同时也面临着错误、内存管理等风险。仅在深入了解且有必要时才推荐使用。博主简介:《SpringBoot技术内幕》技术书籍作者,热爱研究技术,写干货技术文章。公众号:《程序新视野》,博主的公众号,欢迎关注~技术交流:请联系博主微信号:zhuan2quan
