1.Java之Unsafe-越迷人的捌点越危险
Java之Unsafe-越迷人的越危险
简要介绍:
Java语言先比较与C和C++有一个非常大的不同点在于Java语言无法直接操作内存,实际开发中,源码源码默认都是点位由JVM来进行内存分配和垃圾回收,而JVM在进行垃圾回收的捌点时候,绝大多数垃圾回收器都需要STW(stop the world)这个问题往往会导致服务短暂或者较长时间的源码源码暂停。因此Unsafe提供了通过Java直接操作内存的点位学习系统源码API,尽管Unsafe是捌点JavaNIO和并发的核心类,但是源码源码其如其名,这是点位一个官方不推荐开发者使用的及其不安全的类!
主要作用:序号作用API1内存管理。捌点(包括分配内存、源码源码释放内存等。点位jqueryeach源码)allocateMemory(分配内存)、捌点reallocateMemory(重新分配内存)、源码源码copyMemory(拷贝内存)、点位freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、moduleid源码putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)2非常规的对象实例化allocateInstance()方法提供了另一种创建实例的途径3操作类、对象、变量staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)4数组操作arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的htran源码增量地址)等方法5多线程同步。包括锁机制、CAS操作等monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap6挂起与恢复park、unpark7内存屏障loadFence、storeFence、fullFence一、获取Unsafe源码-基于jdk1.8/**在Unsafe源码中限制了获取Unsafe的ClassLoader,如果这个方法调用实例不是fgets源码由BootStrap类加载器加载的,则会报错*因此,我们如果需要使用Unsafe类,可以通过反射的方式来获取。*/@CallerSensitivepublicstaticUnsafegetUnsafe(){ Classvar0=Reflection.getCallerClass();//此处会判断ClassLoader是否为空,BootStrap由C语言编写,在Java中获取会返回null。if(!VM.isSystemDomainLoader(var0.getClassLoader())){ thrownewSecurityException("Unsafe");}else{ returntheUnsafe;}}获取方式/***反射获取Unsafe**@returnUnsafe*/publicstaticfinalUnsafegetUnsafe(){ Unsafeunsafe=null;try{ FieldtheUnsafe=Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe=(Unsafe)theUnsafe.get(null);}catch(NoSuchFieldException|IllegalAccessExceptione){ e.printStackTrace();}returnunsafe;}二、操作方法数组操作packagecom.liziba.unsafe;importsun.misc.Unsafe;/***<p>*操作数组示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateArrayExample{ /***1、publicnativeintarrayBaseOffset(Class<?>var1);获取数组第一个元素的偏移地址*2、publicnativeintarrayIndexScale(Class<?>var1);获取数组中元素的增量地址*3、publicObjectgetObject(Objectvar1,intvar2);通过对象和地址偏移量获取元素*/publicstaticvoidoperateArrayUseUnsafe(){ //测试数组String[]exampleArray=newString[]{ "李","子","捌"};Unsafeunsafe=UnsafeFactory.getUnsafe();//获取数组的基本偏移量intbaseOffset=unsafe.arrayBaseOffset(String[].class);System.out.println("String[]baseoffsetis:"+baseOffset);//获取数组中元素的增量地址intscale=unsafe.arrayIndexScale(String[].class);System.out.println("String[]indexscaleis:"+scale);//获取数组中第n个元素i=(baseOffset+(scale*n-1))System.out.println("thirdelementis:"+unsafe.getObject(exampleArray,baseOffset+(scale*2)));//修改数组中第n个元素i=(baseOffset+(scale*n-1))unsafe.putObject(exampleArray,baseOffset+scale*2,"柒");System.out.println("thirdelementis:"+unsafe.getObject(exampleArray,baseOffset+(scale*2)));}publicstaticvoidmain(String[]args){ OperateArrayExample.operateArrayUseUnsafe();}}输出结果
对象操作packagecom.liziba.unsafe;importcom.liziba.unsafe.pojo.User;importsun.misc.Unsafe;importjava.io.File;importjava.io.FileInputStream;importjava.lang.reflect.Constructor;importjava.lang.reflect.Field;/***<p>*操作对象示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateObjectExample{ /***1、publicnativeObjectallocateInstance(Class<?>var1);分配内存*2、publicnativeClass<?>defineClass(Stringvar1,byte[]var2,intvar3,intvar4,ClassLoadervar5,ProtectionDomainvar6);方法定义一个类用于动态的创建类*@throwsException*/publicstaticvoidoperateObjectUseUnsafe()throwsException{ Unsafeunsafe=UnsafeFactory.getUnsafe();//使用Unsafe的allocateInstance()方法,可以无需使用构造函数的情况下实例化对象Useruser=(User)unsafe.allocateInstance(User.class);user.setId(1);user.setName("李子捌");System.out.println(user);//返回对象成员属性在内存中相对于对象在内存中地址的偏移量Fieldname=User.class.getDeclaredField("name");longfieldOffset=unsafe.objectFieldOffset(name);//使用Unsafe的putXxx()方法,可以直接修改内存地址指向的数据(可以越过权限访问控制符)unsafe.putObject(user,fieldOffset,"李子柒");System.out.println(user);//使用Unsafe在运行时通过.class文件,创建类FileclassFile=newFile("E:\workspaceall\liziba-javap5\out\production\liziba-javap5\com\liziba\unsafe\pojo\User.class");FileInputStreamfis=newFileInputStream(classFile);byte[]classContent=newbyte[(int)classFile.length()];fis.read(classContent);Class<?>clazz=unsafe.defineClass(null,classContent,0,classContent.length,null,null);Constructor<?>constructor=clazz.getDeclaredConstructor(int.class,String.class);System.out.println(constructor.newInstance(1,"李子玖"));}publicstaticvoidmain(String[]args){ try{ OperateObjectExample.operateObjectUseUnsafe();}catch(Exceptione){ e.printStackTrace();}}}输出结果
内存操作packagecom.liziba.unsafe;importsun.misc.Unsafe;/***<p>*内存地址操作示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateMemoryExample{ /***1、publicnativelongallocateMemory(longvar1);分配var1字节大小的内存,返回起始地址偏移量*2、publicnativelongreallocateMemory(longvar1,longvar3);重新给var1起始地址的内存分配长度为var3字节的内存,返回新的内存起始地址偏移量*3、publicnativevoidfreeMemory(longvar1);释放起始地址为var1的地址**分配地址的方法还有重分配,都是分配在堆外内存,返回的是一个long类型的地址偏移量。这个偏移量在Java程序中的每一块内存都是唯一的**/publicstaticvoidoperateMemoryUseUnsafe(){ Unsafeunsafe=UnsafeFactory.getUnsafe();//申请分配8byte的内存longaddress=unsafe.allocateMemory(1L);//初始化内存填充值unsafe.putByte(address,(byte)1);//测试输出System.out.println(newStringBuilder().append("address:").append(address).append("bytevalue:").append(unsafe.getByte(address)));//重新分配一个地址longnewAddress=unsafe.reallocateMemory(address,8L);unsafe.putLong(newAddress,L);System.out.println(newStringBuilder().append("address:").append(newAddress).append("longvalue:").append(unsafe.getLong(newAddress)));//释放地址,注意地址可能被其他使用unsafe.freeMemory(newAddress);System.out.println(newStringBuilder().append("address:").append(newAddress).append("longvalue:").append(unsafe.getLong(newAddress)));}publicstaticvoidmain(String[]args){ OperateMemoryExample.operateMemoryUseUnsafe();}}输出结果
CAS操作packagecom.liziba.unsafe;importcom.liziba.unsafe.pojo.User;importsun.misc.Unsafe;importjava.lang.reflect.Field;/***<p>*CAS操作示例*</p>**@Author:Liziba*@Date:/5/:*/publicclassOperateCASExample{ /***CAS==compareandswap(比较并替换)*当需要改变的值为期望值的时候,就替换为新的值,是原子(不可再分割)操作。Java中大量的并发框架底层使用到了CAS操作。*优势:无锁操作,减少线程切换带来的开销*缺点:CAS容易在并发的情况下失败从而引发性能问题,也存在ABA问题。**Unsafe中提供了三个方法*1、compareAndSwapInt*2、compareAndSwapLong*3、compareAndSwapObject**/publicstaticvoidoperateCASUseUnsafe()throwsException{ Useruser=newUser(1,"李子捌");System.out.println("preuservalue:"+user);Unsafeunsafe=UnsafeFactory.getUnsafe();Fieldid=user.getClass().getDeclaredField("id");Fieldname=user.getClass().getDeclaredField("name");//获取ID字段的内存偏移量longidFieldOffset=unsafe.objectFieldOffset(id);//获取name字段的内存偏移量longnameFieldOffset=unsafe.objectFieldOffset(name);//如果ID的期望值是1,则修改为successunsafe.compareAndSwapInt(user,idFieldOffset,1,);//如果name的期望值是小荔枝,则修改为李子柒failunsafe.compareAndSwapObject(user,nameFieldOffset,"小荔枝","李子柒");//输出修改的user对象System.out.println("postuservalue:"+user);}publicstaticvoidmain(String[]args){ try{ OperateCASExample.operateCASUseUnsafe();}catch(Exceptione){ e.printStackTrace();}}}输出结果
线程的挂起和恢复/***查看Java的java.util.concurrent.locks.LockSupport源代码可以发现LockSupport类*中有各种版本的pack方法但是最终都是通过调用Unsafe.park()方法实现的。*/publicclassLockSupport{ publicstaticvoidunpark(Threadthread){ if(thread!=null)UNSAFE.unpark(thread);}publicstaticvoidpark(Objectblocker){ Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(false,0L);setBlocker(t,null);}publicstaticvoidparkNanos(Objectblocker,longnanos){ if(nanos>0){ Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(false,nanos);setBlocker(t,null);}}publicstaticvoidparkNanos(longnanos){ if(nanos>0)UNSAFE.park(false,nanos);}publicstaticvoidparkUntil(Objectblocker,longdeadline){ Threadt=Thread.currentThread();setBlocker(t,blocker);UNSAFE.park(true,deadline);setBlocker(t,null);}publicstaticvoidparkUntil(longdeadline){ UNSAFE.park(true,deadline);}}我们平时如何实现浅克隆?
实现Closeable接口
重写close()方法
一、Unsafe实现浅克隆浅克隆工具类packagecom.liziba.unsafe.clone;importcom.liziba.unsafe.UnsafeFactory;importsun.misc.Unsafe;importjava.lang.reflect.Field;importjava.lang.reflect.Modifier;importjava.util.Arrays;/***<p>*浅克隆工具类*</p>**@Author:Liziba*@Date:/5/:*/publicclassShallowCloneUtil{ /***获取对象的内存地址**@Description*Unsafe类没有提供直接获取实例对象内存地址的方法,但是可以通过以下方式间接获取。*构建对象A,A包含了我们需要获取内存地址的B对象的引用,这样只有获取到A对象持有的B对象的引用地址,就可以知道B对象的地址了。*我们可以通过Unsafe类获取内存地址的方法publicnativelonggetLong(Objectvar1,longvar2)来获取;*此处我们为了方便,通过数组Object[]添加Object元素,持有Object的引用**@return*/publicstaticLonggetAddress(Objectobj){ Object[]objects=newObject[]{ obj};Unsafeunsafe=UnsafeFactory.getUnsafe();intarrayBaseOffset=unsafe.arrayBaseOffset(Object[].class);returnunsafe.getLong(objects,arrayBaseOffset);}/***获取对象的大小**@Dscription*Java中实例化一个对象时,JVM会在堆中分配非static的Field的内存,其他的static属性或者method在类加载期间或者JVM启动时已经存放在内存中。*所以我们计算对象的大小的时候只需要求和Field的大小就行了,JVM分配内存时,单个实例对象中的Field内存是连续不断地,*因此我们只需获取最大偏移量Filed的偏移量+最大偏移量Filed本身的大小即可**Java中基本数据类型所占的字节数*byte/boolean1字节*char/short2字节*int/float4字节*long/double8字节*boolean理论上占1/8字节,实际上按照1byte处理。*Java采用的是Unicode编码,每一个字节占8位,一个字节由8个二进制位组成。**@paramclazz*@return*/publicstaticLongsize(Classclazz){ //最后一个Filed的内存偏移量longmaxOffset=0;ClasslastFiledClass=null;Unsafeunsafe=UnsafeFactory.getUnsafe();do{ for(Fieldfield:clazz.getDeclaredFields()){ if(!Modifier.isStatic(field.getModifiers())){ longtmpOffset=unsafe.objectFieldOffset(field);if(tmpOffset>maxOffset){ maxOffset=tmpOffset;lastFiledClass=field.getType();}}}}while((clazz=clazz.getSuperclass())!=null);//最后一个Field本身的大小intlastFiledSize=(boolean.class.equals(lastFiledClass)||byte.class.equals(lastFiledClass))?1:(short.class.equals(lastFiledClass)||char.class.equals(lastFiledClass))?2:(int.class.equals(lastFiledClass)||float.class.equals(lastFiledClass))?4:8;returnmaxOffset+lastFiledSize;}/***申请一块固定大小的内存空间**@Description*通过Unsafe的publicnat