java:Unsafe
1 前言
底层的源码中,经常可见Unsafe,Unsafe类是java用于内存管理的类,但操作不当,可能导致内存泄漏等问题,官方亦不推荐过多使用Unsafe,故而名为Unsafe。
2 简单使用
观察Unsafe代码可知,Unsafe的实例对象theUnsafe是static静态代码块初始化,且构造方法私有,仅提供getUnsafe()供外部调用,但是getUnsafe()中会判断!VM.isSystemDomainLoader(var0.getClassLoader())满足时,将抛出new SecurityException(“Unsafe”)异常。
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
theUnsafe = new Unsafe();
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();
}
判断是否为最顶层的类加载器:
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
在双亲委派机制中,我们知道,最顶层的类加载器,就是Bootstrap ClassLoader,而上面判断非最顶层类加载器将抛错,如下简单演示下类加载器:
package com.xiaoxu.lock;
/**
* @author xiaoxu
* @date 2022-09-29
* spring_boot:com.xiaoxu.lock.Shuangqing
*/
public class Shuangqing {
public static void main(String[] args) {
// Application ClassLoader 应用程序类加载器(上层为扩展类加载器)
ClassLoader classLoader = Shuangqing.class.getClassLoader();
System.out.println(classLoader);
// Extension ClassLoader 扩展类加载器(第二层,上层为启动类加载器)
System.out.println(classLoader.getParent());
// Bootstrap ClassLoader 启动类加载器(最顶层)
System.out.println(classLoader.getParent().getParent());
}
}
执行结果如下,可知最顶层的Bootstrap ClassLoader加载器返回的是null:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@7cd84586
null
如果我们直接在我们的应用代码里,使用Unsafe.getUnsafe()会报错SecurityException(“Unsafe”)。
执行报错:
可尝试反射获取theUnsafe,并演示部分常用的api,如staticFieldOffset、objectFieldOffset、compareAndSwapInt:
package com.xiaoxu.lock;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author xiaoxu
* @date 2022-09-29
* spring_boot:com.xiaoxu.lock.UnsafeUse
*/
public class UnsafeUse {
private int a;
private int b;
private static final int c;
static {
c = 300;
}
public int getA() {
return a;
}
// public void setA(int a) {
// this.a = a;
// }
public int getB() {
return b;
}
// public void setB(int b) {
// this.b = b;
// }
public static Unsafe getUnsafe(){
Unsafe unsafe = null;
try{
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
// 反射获取static类型的成员变量(field.get(null)获取static变量,实例成员变量要传入实例对象)
unsafe = (Unsafe) theUnsafeField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
System.out.println(e.getMessage());
}
return unsafe;
}
@Override
public String toString(){
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
public static void useMe(){
Unsafe unsafe = getUnsafe();
System.out.println(unsafe);
UnsafeUse use = new UnsafeUse();
System.out.println(use);
// unsafe.objectFieldOffset针对实例成员变量,unsafe.staticFieldOffset针对static变量
try {
System.out.println("c:"+UnsafeUse.c);
// 注意:compareAndSwapInt修改静态成员变量的值时,传入类.class即可
unsafe.compareAndSwapInt(UnsafeUse.class,unsafe.staticFieldOffset(UnsafeUse.class.getDeclaredField("c")),
300,444);
System.out.println("修改后的c:"+UnsafeUse.c);
long valueOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("b"));
System.out.println("b的内存偏移地址:"+valueOffset);
System.out.println("b的初始值:"+use.getB());
// compareAndSwapInt:第1个参数是实例对象,第2个参数是实例对象的实例成员变量b的内存偏移值,
// 第3个参数是实例对象的b现在的值,第4个参数是实例对象的b希望update的值,
// 如果第3个参数==实例对象的b现在的值,才更新b的值为第4个参数
unsafe.compareAndSwapInt(use,valueOffset,0,177);
unsafe.compareAndSwapInt(use,valueOffset,177,172);
// compare比较失败,当前的b是172,不是171,更新失败,b仍为172
// CAS更新的方式,不依赖于set方法!! CAS是底层操作数据的方式
unsafe.compareAndSwapInt(use,valueOffset,171,11);
System.out.println("修改后的b:"+use.getB());
} catch (NoSuchFieldException e) {
System.out.println("获取b失败");
}
}
public static void main(String[] args) {
useMe();
}
}
执行结果如下:
sun.misc.Unsafe@30dae81
UnsafeUse[a=0,b=0]
c:300
修改后的c:444
b的内存偏移地址:16
b的初始值:0
修改后的b:172
且compareAndSwapInt的更新是安全可靠的(具有原子性),故而java并发编程的JUC(java.util.concurrent)工具包的底层源码中,常见Unsafe的使用。
3 其他常用方法
3.1 unsafe.putOrderedObject:
public native void putOrderedObject(Object var1, long var2, Object var4)
unsafe.putOrderedObject本质上就是替换对象var1偏移位置是var2处的对象为var4,具有禁止指令重排特性,但不具有可见性。
针对可见性,这里先提前引入一些概念,在java中,变量都是存储在主内存中(包括多个线程共享的变量),对于共享变量,线程不是直接去操作主内存中的变量,而是每个线程单独存在一个工作内存,线程会拷贝主内存中的变量作为副本放入到工作内存中,实际线程是操作工作内存中的变量的副本,所以就可能出现"脏读"的现象,比如共享变量5,线程A读取共享变量5,其工作内存是5,修改其为6,但是此时还没刷新到主内存中,此时线程B读取共享变量,那么读取到的变量就是主内存中的5,而不是6,出现脏读,这里线程A修改共享变量5没有立马刷新到主内存中,即可视为不可见性。
解决可见性的方式,有volatile关键字。java中的volatile关键字具有可见性和禁止指令重排的特性,但是不具有原子性(故而高并发下使用volatile,需要考虑加锁),而可见性就是指,当写一个volatile变量时,JMM(java内存模型)会把该线程对应的本地内存中的共享变量值,立即刷新到主内存中,当读一个volatile变量时,JMM(java内存模型)会把该线程对应的本地内存设置为无效,重新回主内存中读取,但是volatile会造成漏写,是非原子性的,需加锁。
如下示例说明unsafe.putOrderedObject是不具有可见性的,以及其简单的功能使用:
package com.xiaoxu.lock;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
import java.util.concurrent.TimeUnit;
/**
* @author xiaoxu
* @date 2022-10-06 19:25
* spring_boot:com.xiaoxu.lock.UnsafeUse2
*/
public class UnsafeUse2 {
// volitale:具有可见性和指令禁止重排性质,没有原子性
// static volatile boolean flag = true;
// 如果是 static boolean flag = true; unsafe.putOrderedObject将无法修改flag值
// static volatile Boolean flag = true; 具有可见性和禁止指令重排,搭配 unsafe.putOrderedObject
// 就可以达到替换对象的同时,还具有可见性
static Boolean flag = true;
static Unsafe unsafe;
static long FLAG;
static {
// spring-core-5.3.9中有封装好的unsafe的工具类
try {
unsafe = UnsafeUtils.getUnsafe();
FLAG = unsafe.staticFieldOffset(UnsafeUse2.class.getDeclaredField("flag"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void testOne(){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"开始执行");
while(flag){}
System.out.println(Thread.currentThread().getName()+"结束执行");
},"xiaoxu1").start();
try {
TimeUnit.MILLISECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = false;
System.out.println("main线程修改完了flag");
}
public static void testOneByPutOrdered(){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"开始执行");
while(flag){}
System.out.println(Thread.currentThread().getName()+"结束执行");
},"xiaoxu1").start();
try {
TimeUnit.MILLISECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
// unsafe.putOrderedObject具有禁止指令重排的效果,但是不具有可见性
unsafe.putOrderedObject(UnsafeUse2.class,FLAG,false);
// flag = false;
System.out.println("修改后的UnsafeUse2的flag:"+flag);
System.out.println("测试putOrderedObject不具有可见性,main线程修改完了flag");
}
public static void main(String[] args) {
testOne();
// testOneByPutOrdered();
}
}
执行testOne(),可见因为static变量值flag不是可见性的,没有立马回写到主内存中,即便main线程修改了flag为false,线程还是在执行while循环,因为只是线程的工作内存修改了值,但是主内存中实际还未刷新:
上述代码修改变量flag为如下,即可立即回写到主内存中:
static volatile Boolean flag = true;
再次执行:
为了证明unsafe.putOrderedObject不具有可见性,以及演示下功能使用,还是保留如下:
static Boolean flag = true;
再次执行testOneByPutOrdered():
可见unsafe.putOrderedObject已经将flag修改为了false,但是实际线程还是在while循环里(仅main主线程可见,而其他子线程不可见),故而其是不可见的。
故而高并发中,需要保证可见性的同时更改对象,建议unsafe.putOrderedObject和volatile关键字一起使用(当然还需要考虑加锁),部分源码中可见,如CompletableFuture:
abstract static class Completion extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
volatile Completion next; // Treiber stack link
/**
* Performs completion action if triggered, returning a
* dependent that may need propagation, if one exists.
*
* @param mode SYNC, ASYNC, or NESTED
*/
abstract CompletableFuture<?> tryFire(int mode);
/** Returns true if possibly still triggerable. Used by cleanStack. */
abstract boolean isLive();
public final void run() { tryFire(ASYNC); }
public final boolean exec() { tryFire(ASYNC); return true; }
public final Void getRawResult() { return null; }
public final void setRawResult(Void v) {}
}
static void lazySetNext(Completion c, Completion next) {
UNSAFE.putOrderedObject(c, NEXT, next);
}
因为UNSAFE.putOrderedObject是不可见性的,所以方法名为lazySetNext(是延迟回写到主内存中的),但是性能较高,结合较轻的volatile关键字使用效果很好(而不是一味的使用较重的synchronized来达到效果,synchronized也能达到可见性,但是可能造成线程阻塞,较重。《深入理解java虚拟机》一书中提到,对1个变量执行unlock前,必须先将此变量同步回主内存中,所以加锁、释放锁也是可见性的,故而synchronized、ReentrantLock等等的加锁释放锁操作,是可见的)。源码中UNSAFE.putOrderedObject(c, NEXT, next);的偏移地址NEXT,实际上就是取的Completion的volatile Completion next变量,为了保证数据的可见性,故而加上了volatile关键字保证可见性。
3.2 unsafe.putOrderedInt和unsafe.putOrderedLong:
java中没有C语言的指针概念,故而引入一个后门Unsafe工具类,用于管理内存、操作变量等等。其中unsafe.putOrderedInt和unsafe.putOrderedLong,可以达到为数组中赋值的效果。
Unsafe源码方法如下:
public native void putOrderedInt(Object var1, long var2, int var4);
public native void putOrderedLong(Object var1, long var2, long var4);
提到这两个方法使用前,需要引入别的一些东西,如下:
在Unsafe的源码中,有如下数值:
static {
registerNatives();
Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
theUnsafe = new Unsafe();
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();
}
如theUnsafe.arrayBaseOffset(int[].class),实际上就是指的int数组的偏移地址,然后theUnsafe.arrayIndexScale(int[].class),就是指的int数据,每个数据所占据的偏移大小,即字节大小,我们知道,int类型是4个字节,而long是8个字节,如下所示:
package com.xiaoxu.lock;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
/**
* @author xiaoxu
* @date 2022-10-06 21:03
* spring_boot:com.xiaoxu.lock.UnsafeUse3
*/
public class UnsafeUse3 {
static Unsafe unsafe;
static {
// spring-core-5.3.9中有封装好的unsafe的工具类
try {
unsafe = UnsafeUtils.getUnsafe();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
// int4个字节,long
int intArrayOffset = unsafe.arrayBaseOffset(int[].class);
System.out.println("int数组的偏移地址:"+intArrayOffset);
int k = unsafe.arrayIndexScale(int[].class);
System.out.println("int变量的大小:"+k);
int longArrayOffset = unsafe.arrayBaseOffset(long[].class);
System.out.println("long数组的偏移地址:"+longArrayOffset);
int long1 = unsafe.arrayIndexScale(long[].class);
System.out.println("long变量的大小:"+long1);
}
}
执行结果如下:
int数组的偏移地址:16
int变量的大小:4
long数组的偏移地址:16
long变量的大小:8
可见Unsafe类获取的到int、long的大小,是符合预期的,其他的同理。
另外在《深入理解java虚拟机》一书的对象内存布局中提到,HotSpot虚拟机(是Sun/OracleJDK和OpenJDK中默认的java虚拟机)中,对象在堆内存中的存储布局可划分成三个部分:对象头(Header)、实例数据(Instance data)和对齐填充(padding)。
想查看变量的内存布局,可以使用如下工具:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
修改代码如下:
package com.xiaoxu.lock;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
/**
* @author xiaoxu
* @date 2022-10-06 21:03
* spring_boot:com.xiaoxu.lock.UnsafeUse3
*/
public class UnsafeUse3 {
static Unsafe unsafe;
static {
// spring-core-5.3.9中有封装好的unsafe的工具类
try {
unsafe = UnsafeUtils.getUnsafe();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void test(){
int[] x = new int[3];
System.out.println(ClassLayout.parseInstance(x).toPrintable());
int[] y = new int[5];
System.out.println(ClassLayout.parseInstance(y).toPrintable());
}
public static void main(String[] args) {
// int4个字节,long
int intArrayOffset = unsafe.arrayBaseOffset(int[].class);
System.out.println("int数组的偏移地址:"+intArrayOffset);
int k = unsafe.arrayIndexScale(int[].class);
System.out.println("int变量的大小:"+k);
int longArrayOffset = unsafe.arrayBaseOffset(long[].class);
System.out.println("long数组的偏移地址:"+longArrayOffset);
int long1 = unsafe.arrayIndexScale(long[].class);
System.out.println("long变量的大小:"+long1);
test();
}
}
执行结果:
int数组的偏移地址:16
int变量的大小:4
long数组的偏移地址:16
long变量的大小:8
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 03 00 00 00 (00000011 00000000 00000000 00000000) (3)
16 12 int [I.<elements> N/A
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
16 20 int [I.<elements> N/A
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
说到对齐填充,这个不是必须有的,《深入理解java虚拟机》中还提到,HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是说任何对象的大小都必须是8字节的整数倍,所以上述new int[3]因为对象头和实例数据是28,+4才是32(4*8),所以对齐填充为4,其余同理,如果对象头和实例数据刚好是8的整数倍,那么就没有对齐填充。
介于上述说明,那么操作数组就显而易见了。我们知道数组是有首地址的,也就是上述所说的对象头,数组元素则位于实例数据处,根据大小作偏移即可。由此可有下述操作:
package com.xiaoxu.lock;
import org.openjdk.jol.info.ClassLayout;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import sun.misc.Unsafe;
import java.util.Arrays;
/**
* @author xiaoxu
* @date 2022-10-06 21:03
* spring_boot:com.xiaoxu.lock.UnsafeUse3
*/
public class UnsafeUse3 {
static Unsafe unsafe;
static {
// spring-core-5.3.9中有封装好的unsafe的工具类
try {
unsafe = UnsafeUtils.getUnsafe();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void test(){
int[] x = new int[3];
System.out.println(ClassLayout.parseInstance(x).toPrintable());
int[] y = new int[5];
System.out.println(ClassLayout.parseInstance(y).toPrintable());
System.out.println("unsafe获取的数组首地址:"+unsafe.arrayBaseOffset(int[].class)
+";ClassLayout观察所得int数组的首地址:16;两者一致.");
// 比如x,希望向索引为1的位置插入数据,那么偏移1个单位的int字节长度即可
// 如果是为x[0]插入数据,在首地址的16偏移位置(起始位置)插入即可
unsafe.putOrderedInt(x,16+4*2,355);
unsafe.putOrderedInt(x,16+4*2,125);
unsafe.putOrderedInt(x,16,999);
// 同理,为x[1]插入数据
unsafe.putOrderedInt(x,16+4,777);
System.out.println(Arrays.toString(x));
}
public static void main(String[] args) {
// int4个字节,long
int intArrayOffset = unsafe.arrayBaseOffset(int[].class);
System.out.println("int数组的偏移地址:"+intArrayOffset);
int k = unsafe.arrayIndexScale(int[].class);
System.out.println("int变量的大小:"+k);
int longArrayOffset = unsafe.arrayBaseOffset(long[].class);
System.out.println("long数组的偏移地址:"+longArrayOffset);
int long1 = unsafe.arrayIndexScale(long[].class);
System.out.println("long变量的大小:"+long1);
test();
}
}
执行结果如下:
int数组的偏移地址:16
int变量的大小:4
long数组的偏移地址:16
long变量的大小:8
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 03 00 00 00 (00000011 00000000 00000000 00000000) (3)
16 12 int [I.<elements> N/A
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[I object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
16 20 int [I.<elements> N/A
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
unsafe获取的数组首地址:16;ClassLayout观察所得int数组的首地址:16;两者一致.
[999, 777, 125]
unsafe.putOrderedLong同理。
3.3 unsafe.getAndSetObject:
unsafe的getAndSetObject,针对的是引用类型的对象(非primitive类型,如int等不使用该方法),返回值是未修改前的引用值,然后执行set操作,故而名为getAndSetObject。
源码示例(AtomicReference):
@SuppressWarnings("unchecked")
public final V getAndSet(V newValue) {
return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
}
实际使用如下所示:
package com.xiaoxu.lock;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author xiaoxu
* @date 2022-09-29
* spring_boot:com.xiaoxu.lock.UnsafeUse
*/
public class UnsafeUse {
private int a;
private int b;
volatile private Integer d;
private static final int c;
static {
c = 300;
}
public int getA() {
return a;
}
// public void setA(int a) {
// this.a = a;
// }
public int getB() {
return b;
}
// public void setB(int b) {
// this.b = b;
// }
// public Integer getD() {
// return d;
// }
//
// public void setD(Integer d) {
// this.d = d;
// }
public static Unsafe getUnsafe(){
Unsafe unsafe = null;
try{
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
// 反射获取static类型的成员变量(field.get(null)获取static变量,实例成员变量要传入实例对象)
unsafe = (Unsafe) theUnsafeField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
System.out.println(e.getMessage());
}
return unsafe;
}
@Override
public String toString(){
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
public static void getAndSetObj(){
Unsafe unsafe = getUnsafe();
long aOffset;
long dOffset;
try {
aOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("a"));
dOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("d"));
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
UnsafeUse instance = new UnsafeUse();
System.out.println("初始对象:"+instance+";hash:"+instance.hashCode());
UnsafeUse instance2 = new UnsafeUse();
unsafe.compareAndSwapInt(instance2,aOffset,0,188);
System.out.println("需要替换的对象:"+instance2+";hash:"+instance2.hashCode());
// a虽然是int类型,非引用类型,但是 unsafe.getAndSetObject(instance, aOffset, 9988) 依然返回的是null
System.out.println(unsafe.getAndSetObject(instance, aOffset, 9988));
// unsafe.getAndSetObject:仅针对引用类型使用,非引用使用有误
System.out.println("初始对象被替换成:"+instance+";hash:"+instance.hashCode());
System.out.println("*************************");
UnsafeUse instance3 = new UnsafeUse();
System.out.println("初始对象:"+instance3+";hash:"+instance3.hashCode());
UnsafeUse instance4 = new UnsafeUse();
// compareAndSwapObject:引用型的Integer使用,int不能使用;compareAndSwapInt:int使用,引用型的Integer不能使用
unsafe.compareAndSwapObject(instance4,dOffset,null,776);
System.out.println("需要替换的对象:"+instance4+";hash:"+instance4.hashCode());
Integer number = (Integer)unsafe.getAndSetObject(instance3, dOffset, 9977);
// 因为是引用类型,且没有赋值,所以初始是null (number)
System.out.println(number);
Object andSetObject = unsafe.getAndSetObject(instance3, dOffset, 9933);
// getAndSetObject:先返回初始值,再赋值,所以如下打印的是 :obj:9977
System.out.println("obj:"+andSetObject);
Integer y = (Integer) unsafe.getAndSetObject(instance3, dOffset, 9944);
// 如下打印9933,但是最终的 instance3:d==9944
System.out.println(y);
System.out.println("目前的初始对象:"+instance3+";hash:"+instance3.hashCode());
}
public static void useMe(){
Unsafe unsafe = getUnsafe();
System.out.println(unsafe);
UnsafeUse use = new UnsafeUse();
System.out.println(use);
// unsafe.objectFieldOffset针对实例成员变量,unsafe.staticFieldOffset针对static变量
try {
System.out.println("c:"+UnsafeUse.c);
// 注意:compareAndSwapInt修改静态成员变量的值时,传入类.class即可
unsafe.compareAndSwapInt(UnsafeUse.class,unsafe.staticFieldOffset(UnsafeUse.class.getDeclaredField("c")),
300,444);
System.out.println("修改后的c:"+UnsafeUse.c);
long valueOffset = unsafe.objectFieldOffset(UnsafeUse.class.getDeclaredField("b"));
System.out.println("b的内存偏移地址:"+valueOffset);
System.out.println("b的初始值:"+use.getB());
// compareAndSwapInt:第1个参数是实例对象,第2个参数是实例对象的实例成员变量b的内存偏移值,
// 第3个参数是实例对象的b现在的值,第4个参数是实例对象的b希望update的值,
// 如果第3个参数==实例对象的b现在的值,才更新b的值为第4个参数
unsafe.compareAndSwapInt(use,valueOffset,0,177);
unsafe.compareAndSwapInt(use,valueOffset,177,172);
// compare比较失败,当前的b是172,不是171,更新失败,b仍为172
// CAS更新的方式,不依赖于set方法!! CAS是底层操作数据的方式
unsafe.compareAndSwapInt(use,valueOffset,171,11);
System.out.println("修改后的b:"+use.getB());
} catch (NoSuchFieldException e) {
System.out.println("获取b失败");
}
}
public static void main(String[] args) {
getAndSetObj();
}
}
执行效果如下:
初始对象:UnsafeUse[a=0,b=0,d=<null>];hash:1232367853
需要替换的对象:UnsafeUse[a=188,b=0,d=<null>];hash:1789447862
null
初始对象被替换成:UnsafeUse[a=-696965424,b=0,d=<null>];hash:1232367853
*************************
初始对象:UnsafeUse[a=0,b=0,d=<null>];hash:38997010
需要替换的对象:UnsafeUse[a=0,b=0,d=776];hash:1942406066
null
obj:9977
9933
目前的初始对象:UnsafeUse[a=0,b=0,d=9944];hash:38997010