Unsafe是不开源的,但是它无所不能。它能申请堆外内存、cas、park和unpark、修改private变量等等的。
public class UnsafeUtil {
public static Unsafe getUnsafe() throws Exception{
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
return unsafe;
}
}
通过反射获取到unsafe。
unsafe可以根据类对象得到实例。
举例:
public class InitializationOrdering {
private long a;
public InitializationOrdering() {
this.a = 1;
}
public long getA() {
return this.a;
}
}
@Test
public void testInitializeInstanceByNew() {
InitializationOrdering initializationOrdering = new InitializationOrdering();
assertEquals(initializationOrdering.getA(), 1);
}
如果是new出来的,必然走构造器,那么getA()
一定是1。
@Test
public void testInitializeInstanceByUnsafe() throws Exception {
Unsafe unsafe = UnsafeUtil.getUnsafe();
InitializationOrdering instance = (InitializationOrdering) unsafe.allocateInstance(InitializationOrdering.class);
assertEquals(instance.getA(), 0);
}
unsafe根据class对象拿到了实例,没有走构造器,很是神奇。
对于私有变量,unsafe也能拿到。
例子:
public class SecretHolder {
private int SECRET_VALUE = 0;
public boolean secretIsDisclosed() {
return SECRET_VALUE == 1;
}
}
@Test
public void testModifyPrivateValue() throws Exception {
SecretHolder secretHolder = new SecretHolder();
Field field = secretHolder.getClass().getDeclaredField("SECRET_VALUE");
Unsafe unsafe = UnsafeUtil.getUnsafe();
unsafe.putInt(secretHolder, unsafe.objectFieldOffset(field), 1);
assertTrue(secretHolder.secretIsDisclosed());
}
unsafe成功地给私有变量赋上了值1。
unsafe可以自己抛出异常,单纯地抛异常:
@Test(expected = IOException.class)
public void throwsAnException() throws Exception {
Unsafe unsafe = UnsafeUtil.getUnsafe();
unsafe.throwException(new IOException());
}
一般我们的对象是new出来的,不管你用什么设计模式,最终还是new出来的。new出来的对象一定在堆上,是处于jvm的管辖范围的,换句话说,它能被gc。
但是有些情况,我们要在堆外直接申请内存放置对象。它脱离了gc,并且空间可以要得很大。
但是我们要手动释放内存。
public class OffHeapArray {
private final static int BYTE = 1;
private long size;
private long address;
public OffHeapArray(long size) throws Exception {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) throws Exception {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) throws Exception {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
public void freeMemory() throws Exception {
getUnsafe().freeMemory(address);
}
}
构造方法:
public OffHeapArray(long size) throws Exception {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
分配内存,初始化地址。
set方法:
public void set(long i, byte value) throws Exception {
getUnsafe().putByte(address + i * BYTE, value);
}
这是往索引中放值。这个索引就是地址加上偏移量。
get方法:
public int get(long idx) throws Exception {
return getUnsafe().getByte(address + idx * BYTE);
}
根据索引取值。
测试:
@Test
@Ignore
public void testOffHeapMemory() throws Exception {
//given
long SUPER_SIZE = (long) Integer.MAX_VALUE;
OffHeapArray array = new OffHeapArray(SUPER_SIZE);
//when
int sum = 0;
for (int i = 0; i < 100; i++) {
array.set((long) Integer.MAX_VALUE + i, (byte) 3);
sum += array.get((long) Integer.MAX_VALUE + i);
}
long arraySize = array.size();
array.freeMemory();
//then
assertEquals(arraySize, SUPER_SIZE);
assertEquals(sum, 300);
}
我们申请的数组长度是整数的最大值。
然后往数组里面不断地放值并取出来累加。
最后一定要手动释放内存。