了解多线程前还是先了解一下Unsafe内存操作不安全类

2022年11月5日12:55:59

了解多线程前还是先了解一下Unsafe内存操作不安全类
简介
Java是面向对象语言,在使用Java编程时,大多数情况下都不会直接操作内存,而且Java也不提倡直接操作内存,但是Java中到底有没有可以直接操作内存的工具类呢?有!Java中提供Unsafe类可以用来来直接操作内存。

Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,直接操作内存就意味着
1、不受jvm管理,也就意味着无法被GC,需要我们手动GC,稍有不慎就会出现内存泄漏。
2、Unsafe的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是JVM崩溃级别的异常,会导致整个JVM实例崩溃,表现为应用程序直接crash掉。
3、直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。

Unsafe 类

publicfinalclassUnsafe

Unsafe类是"final"的,不允许继承。

Unsafe 属性

privatestaticfinalUnsafe theUnsafe;publicstaticfinalint INVALID_FIELD_OFFSET=-1;publicstaticfinalint ARRAY_BOOLEAN_BASE_OFFSET;publicstaticfinalint ARRAY_BYTE_BASE_OFFSET;publicstaticfinalint ARRAY_SHORT_BASE_OFFSET;publicstaticfinalint ARRAY_CHAR_BASE_OFFSET;publicstaticfinalint ARRAY_INT_BASE_OFFSET;publicstaticfinalint ARRAY_LONG_BASE_OFFSET;publicstaticfinalint ARRAY_FLOAT_BASE_OFFSET;publicstaticfinalint ARRAY_DOUBLE_BASE_OFFSET;publicstaticfinalint ARRAY_OBJECT_BASE_OFFSET;publicstaticfinalint ARRAY_BOOLEAN_INDEX_SCALE;publicstaticfinalint ARRAY_BYTE_INDEX_SCALE;publicstaticfinalint ARRAY_SHORT_INDEX_SCALE;publicstaticfinalint ARRAY_CHAR_INDEX_SCALE;publicstaticfinalint ARRAY_INT_INDEX_SCALE;publicstaticfinalint ARRAY_LONG_INDEX_SCALE;publicstaticfinalint ARRAY_FLOAT_INDEX_SCALE;publicstaticfinalint ARRAY_DOUBLE_INDEX_SCALE;publicstaticfinalint ARRAY_OBJECT_INDEX_SCALE;publicstaticfinalint ADDRESS_SIZE;

这些属性都是在类加载时初始化,它们都是一些类型数组指针。

Unsafe 静态加载

static{registerNatives();Reflection.registerMethodsToFilter(Unsafe.class,newString[]{"getUnsafe"});
	theUnsafe=newUnsafe();
	ARRAY_BOOLEAN_BASE_OFFSET= theUnsafe.arrayBaseOffset(boolean[].class);
	ARRAY_BYTE_BASE_OFFSET= theUnsafe.arrayBaseOffset(byte[].class);
	ARRAY_SHORT_BASE_OFFSET= theUnsafe.arrayBaseOffset(short[].class);
	ARRAY_CHAR_BASE_OFFSET= theUnsafe.arrayBaseOffset(char[].class);
	ARRAY_INT_BASE_OFFSET= theUnsafe.arrayBaseOffset(int[].class);
	ARRAY_LONG_BASE_OFFSET= theUnsafe.arrayBaseOffset(long[].class);
	ARRAY_FLOAT_BASE_OFFSET= theUnsafe.arrayBaseOffset(float[].class);
	ARRAY_DOUBLE_BASE_OFFSET= theUnsafe.arrayBaseOffset(double[].class);
	ARRAY_OBJECT_BASE_OFFSET= theUnsafe.arrayBaseOffset(Object[].class);
	ARRAY_BOOLEAN_INDEX_SCALE= theUnsafe.arrayIndexScale(boolean[].class);
	ARRAY_BYTE_INDEX_SCALE= theUnsafe.arrayIndexScale(byte[].class);
	ARRAY_SHORT_INDEX_SCALE= theUnsafe.arrayIndexScale(short[].class);
	ARRAY_CHAR_INDEX_SCALE= theUnsafe.arrayIndexScale(char[].class);
	ARRAY_INT_INDEX_SCALE= theUnsafe.arrayIndexScale(int[].class);
	ARRAY_LONG_INDEX_SCALE= theUnsafe.arrayIndexScale(long[].class);
	ARRAY_FLOAT_INDEX_SCALE= theUnsafe.arrayIndexScale(float[].class);
	ARRAY_DOUBLE_INDEX_SCALE= theUnsafe.arrayIndexScale(double[].class);
	ARRAY_OBJECT_INDEX_SCALE= theUnsafe.arrayIndexScale(Object[].class);
	ADDRESS_SIZE= theUnsafe.addressSize();}privatestaticnativevoidregisterNatives();

Unsafe 构造函数

privateUnsafe(){}

Unsafe 对象不能直接通过 new Unsafe(),它的构造函数是私有的。

Unsafe 实例化方法

publicstaticUnsafegetUnsafe(){Class var0=Reflection.getCallerClass();if(!VM.isSystemDomainLoader(var0.getClassLoader())){thrownewSecurityException("Unsafe");}else{return theUnsafe;}}

getUnsafe 只能从引导类加载器(bootstrap class loader)加载,非启动类加载器直接调用 Unsafe.getUnsafe() 方法会抛出 SecurityException 异常。解决办法:
1、可以令代码 " 受信任 "。运行程序时,通过 JVM 参数设置 bootclasspath 选项,指定系统类路径加上使用的一个 Unsafe 路径。

java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.Test

2、通过 Java 反射机制,暴力获取。

Field field=Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);Unsafe unsafe=(Unsafe) field.get(null);

Unsafe 内存管理

// 获取本地指针的大小(单位是byte),通常值为4或者8。常量ADDRESS_SIZE就是调用此方法。publicnativeintaddressSize();// 获取本地内存的页数,此值为2的幂次方。publicnativeintpageSize();// 分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址。publicnativelongallocateMemory(long var1);// 通过指定的内存地址address重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)。publicnativelongreallocateMemory(long var1,long var3);// 将给定内存块中的所有字节设置为固定值(通常是0)publicnativevoidsetMemory(Object var1,long var2,long var4,byte var6);// 内存复制publicnativevoidcopyMemory(Object var1,long var2,Object var4,long var5,long var7);// 清除内存publicnativevoidfreeMemory(long var1);

注意:allocateMemory方法申请的内存,将直接脱离jvm,gc将无法管理该方式申请的内存,用完一定要手动释放内存,防止内存溢出;
JDK中示例:ByteBuffer.allocateDirect(int capacity)使用DirectByteBuffer,DirectByteBuffer中就是用allocateMemory申请堆外内存。

Unsafe 获取偏移量

// 返回指定变量所属类中的内存偏移量publicnativelongobjectFieldOffset(Field var1);// 获取数组中第一个元素的地址publicnativeintarrayBaseOffset(Class<?> var1);// 获取静态变量地址偏移值publicnativelongstaticFieldOffset(Field var1);// 其实就是数据中元素偏移地址的增量,数组中的元素的地址是连续的publicnativeintarrayIndexScale(Class<?> var1);

Unsafe 检查类初始化

// 检测给定的类是否需要初始化。// 当ensureClassInitialized方法不生效的时候才返回falsepublicnativebooleanshouldBeInitialized(Class<?> c);// 检测给定的类是否已经初始化。publicnativevoidensureClassInitialized(Class<?> c);

Unsafe 从指定位置读取

// 从指定内存地址处开始读取一个bytepublicnativebytegetByte(long var1);// 从指定内存地址处开始读取一个shortpublicnativeshortgetShort(long var1);// 从指定内存地址处开始读取一个charpublicnativechargetChar(long var1);// 从指定内存地址处开始读取一个intpublicnativeintgetInt(long var1);// 从指定内存地址处开始读取一个longpublicnativelonggetLong(long var1);// 从指定内存地址处开始读取一个floatpublicnativefloatgetFloat(long var1);// 从指定内存地址处开始读取一个doublepublicnativedoublegetDouble(long var1);

Unsafe 向指定位置写值

// 向指定位置写入一个intpublicnativevoidputInt(long var1,int var3);// 向指定位置写入一个charpublicnativevoidputChar(long var1,char var3);// 向指定位置写入一个bytepublicnativevoidputByte(long var1,byte var3);// 向指定位置写入一个shortpublicnativevoidputShort(long var1,short var3);// 向指定位置写入一个longpublicnativevoidputLong(long var1,long var3);// 向指定位置写入一个floatpublicnativevoidputFloat(long var1,float var3);// 向指定位置写入一个doublepublicnativevoidputDouble(long var1,double var3);

Unsafe 对象操作
从指定偏移量处读取对象属性(非主存)

publicnativeintgetInt(Object var1,long var2);publicnativeObjectgetObject(Object var1,long var2);publicnativebooleangetBoolean(Object var1,long var2);publicnativebytegetByte(Object var1,long var2);publicnativeshortgetShort(Object var1,long var2);publicnativechargetChar(Object var1,long var2);publicnativelonggetLong(Object var1,long var2);publicnativefloatgetFloat(Object var1,long var2);publicnativedoublegetDouble(Object var1,long var2);

向指定偏移量处修改对象属性(非主存)

publicnativevoidputInt(Object var1,long var2,int var4);publicnativevoidputObject(Object var1,long var2,Object var4);publicnativevoidputBoolean(Object var1,long var2,boolean var4);publicnativevoidputByte(Object var1,long var2,byte var4);publicnativevoidputShort(Object var1,long var2,short var4);publicnativevoidputChar(Object var1,long var2,char var4);publicnativevoidputLong(Object var1,long var2,long var4);publicnativevoidputFloat(Object var1,long var2,float var4);publicnativevoidputDouble(Object var1,long var2,double var4);

向指定偏移量处修改对象属性(主存)

publicnativeObjectgetObjectVolatile(Object var1,long var2);publicnativeintgetIntVolatile(Object var1,long var2);publicnativebooleangetBooleanVolatile(Object var1,long var2);publicnativebytegetByteVolatile(Object var1,long var2);publicnativeshortgetShortVolatile(Object var1,long var2);publicnativechargetCharVolatile(Object var1,long var2);publicnativelonggetLongVolatile(Object var1,long var2);publicnativefloatgetFloatVolatile(Object var1,long var2);publicnativedoublegetDoubleVolatile(Object var1,long var2);

向指定偏移量处修改对象属性(主存)

publicnativevoidputObjectVolatile(Object var1,long var2,Object var4);publicnativevoidputIntVolatile(Object var1,long var2,int var4);publicnativevoidputBooleanVolatile(Object var1,long var2,boolean var4);publicnativevoidputByteVolatile(Object var1,long var2,byte var4);publicnativevoidputShortVolatile(Object var1,long var2,short var4);publicnativevoidputCharVolatile(Object var1,long var2,char var4);publicnativevoidputLongVolatile(Object var1,long var2,long var4);publicnativevoidputFloatVolatile(Object var1,long var2,float var4);publicnativevoidputDoubleVolatile(Object var1,long var2,double var4);publicnativevoidputOrderedObject(Object var1,long var2,Object var4);publicnativevoidputOrderedObject(Object var1,long var2,Object var4);publicnativevoidputOrderedInt(Object var1,long var2,int var4);publicnativevoidputOrderedLong(Object var1,long var2,long var4);

Unsafe CAS操作

publicfinalnativebooleancompareAndSwapObject(Object var1,long var2,Object var4,Object var5);publicfinalnativebooleancompareAndSwapInt(Object var1,long var2,int var4,int var5);publicfinalnativebooleancompareAndSwapLong(Object var1,long var2,long var4,long var6);

针对对象进行CAS操作,本质更新对象中指定偏移量的属性,当原值为var4时才会更新成var5并返回true,否则返回false。
举例:volatile i=0;有多个线程修改i的值,A线程只有在i=1时修改为2,如果代码如下

if(i==1){i=2;}

这样时有问题的,if比较完i可能已经被别人修改了,这种场景特别适合CAS,使用CAS代码如下

boolean isUpdate=compareAndSwapInt(object, offset,1,2)

相当于读->判断->写一次搞定(实在不能理解CAS,可以这么理解)

Unsafe 线程的挂起和恢复

publicnativevoidpark(boolean var1,long var2);

阻塞当前线程直到一个unpark方法出现(被调用)、一个用于unpark方法已经出现过(在此park方法调用之前已经调用过)、线程被中断或者time时间到期(也就是阻塞超时)。在time非零的情况下,如果isAbsolute为true,time是相对于新纪元之后的毫秒,否则time表示纳秒。这个方法执行时也可能不合理地返回(没有具体原因)。并发包java.util.concurrent中的框架对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe#park()方法。

publicnativevoidunpark(Object var1);

释放被park创建的在一个线程上的阻塞。这个方法也可以被使用来终止一个先前调用park导致的阻塞。这个操作是不安全的,因此必须保证线程是存活的(thread has not been destroyed)。从Java代码中判断一个线程是否存活的是显而易见的,但是从native代码中这机会是不可能自动完成的。

Unsafe 内存屏障

publicnativevoidloadFence();

在该方法之前的所有读操作,一定在load屏障之前执行完成。

publicnativevoidstoreFence();

在该方法之前的所有写操作,一定在store屏障之前执行完成

publicnativevoidfullFence();

在该方法之前的所有读写操作,一定在full屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能。

Unsafe 其他

publicnativeintgetLoadAverage(double[] loadavg,int nelems);

获取系统的平均负载值,loadavg这个double数组将会存放负载值的结果,nelems决定样本数量,nelems只能取值为1到3,分别代表最近1、5、15分钟内系统的平均负载。如果无法获取系统的负载,此方法返回-1,否则返回获取到的样本数量(loadavg中有效的元素个数)。实验中这个方法一直返回-1,其实完全可以使用JMX中的相关方法替代此方法。

publicnativevoidthrowException(Throwable ee);

绕过检测机制直接抛出异常。

  • 作者:源码猎人
  • 原文链接:https://blog.csdn.net/nan8426/article/details/105676522
    更新时间:2022年11月5日12:55:59 ,共 9194 字。