简单介绍Java中的Unsafe类
概述
sun.misc.Unsafe
类旨在仅由核心Java库(JUC包下的类)而非标准用户使用的底层机制,即不推荐开发者使用,但并不妨碍我们对该类的了解。Unsafe
可以使Java直接进行内存的操作、实例化类与对实例进行属性操作、提供CAS操作等底层操作,下文将对这些功能进行简单的案例演示,实际开发中依旧不推荐使用,只作为Java体系中的一个知识点了解即可。
Unsafe
实例构建
内部类创建Unsafe
类实例时都是调用其静态方法getUnsafe()
,具体源码如下:
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
// 通过ClassLoader判断时内部还是外部调用
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
由于Unsafe
是为了内部使用而设计的,所以如果我们直接在程序中调用getUnsafe()
时会检测类加载器是内部还是外部,外部将直接报SecurityException
异常。当然,我们可以使用反射来获得实例:
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
Unsafe
对象操作
Java中开发构建实例的方法主要有构造函数、反射、实现Cloneable接口及序列化4种,而Unsafe
构建属于官方不推荐使用的,例子如下:
public class UnsafeTest {
private int number;
public UnsafeTest() {
this.number = 1;
}
public static void main(String[] args) throws InstantiationException, NoSuchFieldException {
instanceOperate();
}
public static void instanceOperate() throws InstantiationException, NoSuchFieldException {
Unsafe unsafe = getUnsafe();
UnsafeTest test = (UnsafeTest) unsafe.allocateInstance(UnsafeTest.class);
// 输出0
System.out.println(test.number);
}
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
除了实例化对象外,Unsafe
还可直接操作类属性,如下:
public static void instanceOperate() throws InstantiationException, NoSuchFieldException {
Unsafe unsafe = getUnsafe();
UnsafeTest test = (UnsafeTest) unsafe.allocateInstance(UnsafeTest.class);
// 输出0
System.out.println(test.number);
Field numberField = UnsafeTest.class.getDeclaredField("number");
unsafe.putInt(test, unsafe.objectFieldOffset(numberField), 1);
// 输出1
System.out.println(test.number);
}
上例中的UnsafeTest
类是没有get与set方法的,Unsafe
对属性number
的操作是直接到内存根据对象字段元数据获取偏移,找到实例number
的属性数据进行操作的,实例数据可参考对象布局(JOL)。
Unsafe
操作堆外内存
当应用程序用完了JVM上的可用内存,最终可能会迫使GC进程过于频繁地运行。理想情况下,我们需要一个特殊的内存区域,堆外且不受GC控制。Unsafe
类中的allocateMemory()
方法能够从堆中分配大对象,这意味着GC和JVM将不会看到并考虑此内存。不过该方法分配的内存需要手动管理,并在不再需要时使用freeMemory()
方法正确回收。
例:通过Unsafe
直接在内存上创建数组UnsafeTest.OffHeapArray
static class OffHeapArray {
private final static int BYTE = 1;
private long size;
// 内存起始地址
private long address;
/**
* 创建内存数组
*
* @param size 分配的内存大小
*/
public OffHeapArray(long size) {
this.size = size;
// 分配内存地址
address = getUnsafe().allocateMemory(size * BYTE);
}
/**
* 设置起始内存偏移指定下标后的位置值
*
* @param i 数组下标,内存偏移量
* @param value
*/
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
/**
* 根据数组下标idx计算在内存中的位置,然后获取数组值
*
* @param idx 数组下标,用于计算元素内存相对数组起始地址的偏移量
* @return
*/
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
public void freeMemory() {
getUnsafe().freeMemory(address);
}
}
// 堆外内存测试方法
public static void offHeapTest() {
// long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
long SUPER_SIZE = Integer.MAX_VALUE;
OffHeapArray array = new OffHeapArray(SUPER_SIZE);
int sum = 0;
for (int i = 0; i < 100; i++) {
array.set((long) Integer.MAX_VALUE + i, (byte) 1);
long each = array.get((long) Integer.MAX_VALUE + i);
sum += each;
}
System.out.println("sum:" + sum + ", array.address:" + array.address + ",array.size():" + array.size() + " ,SUPER_SIZE:" + SUPER_SIZE);
System.out.println(ClassLayout.parseInstance(array).toPrintable());
array.freeMemory();
}
OffHeapArray
是一个通过直接在内存上存取数组的实现类,通过Unsafe的allocateMemory(size)
方法获取到分配的内存起始地址,根据分配的内存起始地址计算数组下标在内存中的实际地址。单看以上程序是无法看出分配的内存是在堆上还是堆外的,可以通过参数-VM:-PrintGCDetails
并更改分配的内存大小size
后对比size
更改前后的堆大小变化。
-
size = Integer.MAX_VALUE
,i < 100
,GCPrint如下: -
size = Integer.MAX_VALUE * 2
,i < 1000
,GCPrint如下:
CAS操作
java.concurrent包中的CAS操作(如AtomicInteger)使用了Unsafe中的compareAndSwap()
方法以提供最佳性能。与Java中的标准悲观同步机制相比,该操作广泛用于无锁算法中,该算法可以利用CAS处理器指令提供极大的加速。以下为使用Unsafe的CAS操作实现的一个计数器范例:
public class CASCounter {
private Unsafe unsafe;
private volatile long val = 0;
// 字段val在对象实例内存起始地址的偏移量
private long offset;
public static void main(String[] args) throws NoSuchFieldException, InterruptedException {
CASCounter casCounter = new CASCounter();
ExecutorService executorService = Executors.newFixedThreadPool(1000);
IntStream.range(0, 1000)
.forEach(i -> executorService.execute(casCounter::increment));
executorService.shutdown();
Thread.sleep(1000);
// 输出1000
System.out.println(casCounter.val);
}
private Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public CASCounter() throws NoSuchFieldException {
unsafe = getUnsafe();
// objectFieldOffset:获取
offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("val"));
}
public void increment() {
long oldVal = val;
while (!unsafe.compareAndSwapLong(this, offset, oldVal, oldVal + 1)) {
oldVal = val;
}
}
public long getVal() {
return val;
}
}
Unsafe
线程切换
Unsafe
类中提供了park()
与unpark()
两个方法让JVM用来进行上下文切换线程。当线程正在等待某个操作时,JVM可以使用不安全类中的park()
方法来阻塞该线程,该方法类似于Object.wait()
方法,不过它调用的是本地操作系统代码,因此利用某些体系结构细节来获得最佳性能。当线程被阻塞并需要再次使其可运行时,JVM使用unpark()方法。可以在一些线程池应用中看到这些方法调用,如ForkJoinPool
、LockSupport
、Exchanger
等。
附代码代码
public class UnsafeTest {
private int number;
public UnsafeTest() {
this.number = 1;
}
public static void main(String[] args) {
offHeapTest();
}
public static void offHeapTest() {
// long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
long SUPER_SIZE = Integer.MAX_VALUE;
OffHeapArray array = new OffHeapArray(SUPER_SIZE);
int sum = 0;
for (int i = 0; i < 100; i++) {
array.set((long) Integer.MAX_VALUE + i, (byte) 1);
long each = array.get((long) Integer.MAX_VALUE + i);
sum += each;
}
System.out.println("sum:" + sum + ", array.address:" + array.address + ",array.size():" + array.size() + " ,SUPER_SIZE:" + SUPER_SIZE);
System.out.println(ClassLayout.parseInstance(array).toPrintable());
array.freeMemory();
}
public static void instanceOperate() throws InstantiationException, NoSuchFieldException {
Unsafe unsafe = getUnsafe();
UnsafeTest test = (UnsafeTest) unsafe.allocateInstance(UnsafeTest.class);
// 输出0
System.out.println(test.number);
Field numberField = UnsafeTest.class.getDeclaredField("number");
unsafe.putInt(test, unsafe.objectFieldOffset(numberField), 1);
// 输出1
System.out.println(test.number);
}
public static Unsafe getUnsafe() {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
return (Unsafe) f.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
static class OffHeapArray {
private final static int BYTE = 1;
private long size;
// 内存起始地址
private long address;
/**
* 创建内存数组
*
* @param size 分配的内存大小
*/
public OffHeapArray(long size) {
this.size = size;
// 分配内存地址
address = getUnsafe().allocateMemory(size * BYTE);
}
/**
* 设置起始内存偏移指定下标后的位置值
*
* @param i 数组下标,内存偏移量
* @param value
*/
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
/**
* 根据数组下标idx计算在内存中的位置,然后获取数组值
*
* @param idx 数组下标,用于计算元素内存相对数组起始地址的偏移量
* @return
*/
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
public void freeMemory() {
getUnsafe().freeMemory(address);
}
}
}
上一篇: 淘宝客怎么做好站外推广?
推荐阅读
-
JQuery 中几个类选择器的简单使用介绍
-
全面解释java中StringBuilder、StringBuffer、String类之间的关系
-
简单介绍Python中的JSON模块
-
在Python中利用Pandas库处理大数据的简单介绍
-
MySQL中decimal类型用法的简单介绍
-
Java设计中的Builder模式的介绍
-
简单介绍Ruby中的CGI编程
-
Java日期时间API系列5-----Jdk7及以前的日期时间类TimeUnit在并发编程中的应用
-
Java日期时间API系列12-----Jdk8中java.time包中的新的日期时间API类,日期格式化,常用日期格式大全
-
Java中Date()类 日期转字符串、字符串转日期的问题