- 基本介绍
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,因此对Unsafe的使用一定要慎重。
- 如何获取UnSafe实例
UnSafe
类的构造方法是私有的,因此不能new出来。
在UnSafe
类中,有一个静态方法,但是这个有限制,只有在引导类加载器BootstrapClassLoader
加载时才合法,否则调用会抛出SecurityException
异常。
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
因此如果想要获取UnSafe
实例,有两种可行的方法。
第一,从getUnSafe()
的限制出发,通过Java命令行命令-Xbootclasspath/a
把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe
方法安全的获取Unsafe实例。
第二,在UnSafe
类中,在一个静态成员变量theUnsafe
,可以通过反射的手段去获取。
public static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
return null;
}
}
-
常用的UnSafe功能介绍
-
Class相关
-
allocateInstance()
不用通过new()
去实例化一个对象,不需要调用构造方法,而且直接调用unSafe
的allocateInstance
方法去实例化一个对象。Constructor
类中的newInstance()
方法中的实现也是调用了改方法。
-
-
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public class Player {
private String name;
public Player() {
this.name = "ddd";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) throws Exception {
Unsafe unSafe = getUnSafe();
Player player = (Player) unSafe.allocateInstance(Player.class);
System.out.println(player);
System.out.println(player.getName());
Player player1 = new Player();
System.out.println(player1);
System.out.println(player1.getName());
/** output
* com.zhh.unsafe.Player@610455d6
* null
* com.zhh.unsafe.Player@511d50c0
* ddd
*/
}
public static Unsafe getUnSafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
return null;
}
}
}
-
objectFieldOffset()
回成员属性在内存中的地址相对于对象内存地址的偏移量-
对象操作
put()方法相当于直接修改内存的地址,首先通过0bjectFieldOffset()
拿到内存地址的偏移量,然后在调用put()修改内存地址。
-
对象操作
public native int getInt(Object var1, long var2);
public native void putInt(Object var1, long var2, int var4);
Player player = new Player("zzh", 22);
Unsafe unSafe = getUnSafe();
Field age = Player.class.getDeclaredField("age");
long offset = unSafe.objectFieldOffset(age);
unSafe.putInt(player, offset, 34);
int anInt = unSafe.getInt(player, offset);
System.out.println("age = " + player.getAge());
System.out.println("age2 = " + anInt);
/**
* age = 34
* age2 = 34
*/
- CAS
/*
* @param o 包含要修改field的对象
* @param offset 对象中某field的偏移量
* @param expected 期望值
* @param update 更新值
* @return true | false
*/
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int update);
什么是CAS? 即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
经典应用:Atomic原子类
// 如:AtomicInteger
// static块计算value在内存中的偏移量
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// cas操作
// 直接调用unSafe的方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
-
内存操作
如果在某些情况需要用到堆外内存的话,这个时候UnSafe
类又派上用场了。UnSafe
类的内存操作包括就内存的申请、释放、拷贝等。
//分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
使用堆外内存的原因
对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。
DirectByteBuffer是Java用于实现堆外内存的一个重要类。
-
参考资源