Unsafe类学习笔记
Unsafe 类初识
- Unsafe位于sun.misc包内,看其命名就知道和注重安全性的java jdk无缘,连文档都没,直接就叫‘不安全’ 。Unsafe的特点是可以直接操作堆外内存,可以随意查看及修改JVM中运行时的数据结构,例如查看和修改对象的成员,Unsafe的操作粒度不是类,而是数据和地址。
- 如何获得Unsafe对象,Unsafe类里面可以看到有一个getUnsafe方法:
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
但是很遗憾我们并不能直接调用,因为这个getUnsafe方法会判断当前调用这个方法的对象的类型,如果并非 java.util.concurrent.atomic内的原子类、AbstractQueuedSynchronizer等类型,则会抛出SecurityException不安全异常。因此需要反射来调用这个方法从而获得Unsafe对象实例:
public static Unsafe getUnsafe() {
try {
Field singletonInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singletonInstanceField.setAccessible(true);
return (Unsafe) singletonInstanceField.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
对于scala,也用的sun.misc.Unsafe, 不过它也用反射封装了一下,可以直接
scala.concurrent.util.Unsafe unsafe = scala.concurrent.util.Unsafe.instance
调用
Unsafe类的成员
- 除了上面谈及的getUnsafe会返回Unsafe实例theUnsafe外,Unsafe一共由105个方法组成,大部分都是native方法。下面是一些可能用到的方法:
- 返回低级别内存信息
addressSize()
pageSize()
- 手动获得对象和对象方法
allocateInstance() 避开构造方法生成对象
objectFieldOffset() 获得对象的某个成员的地址偏移量
- 手动获得类或者静态成员
staticFieldOffset() 获得某个静态成员的地址偏移量
defineClass()
defineAnonymousClass()
ensureClassInitialized()
- 手动获得数组
arrayBaseOffset()
arrayIndexScale()
- 同步的低级别基本方法
monitorEnter()
tryMonitorEnter()
monitorExit()
compareAndSwapInt()
putOrderedInt()
- 手动操作内存
allocateMemory()
copyMemory()
freeMemory()
getAddress()
getInt() ,getInt(Object var1, long var2)第一个参数是要get的对象,第二个参数是字段的偏移量
putInt()
- 阻塞和唤醒
pack()
unpack()
Unsafe常用方式
- 避开构造方法初始化对象,使用allocateInstance
Unsafe unsafe = getUnsafe();
final Class aClass = A.class;
A a = (A) unsafe.allocateInstance(aClass);
- 修改对象成员值(内存出错),使用putInt()
A a = new A(12);
Field f = A.class.getDeclaredField("num");
unsafe.putInt(a, unsafe.objectFieldOffset(f), 8);
- 浅复制
/**
* 将对象转化成地址
* @param obj
* @return
*/
private static long toAddress(Object obj) {
Object[] objects = new Object[]{obj};
long baseOffset = getUnsafe().arrayBaseOffset(objects.getClass());
return normalize(getUnsafe().getInt(objects, baseOffset));
}
/**
* 将地址转化成对象
* @param address
* @return
*/
private static Object fromAddress(long address) {
Object[] objects = new Object[]{null};
long baseOffset = getUnsafe().arrayBaseOffset(objects.getClass());
getUnsafe().putLong(objects, baseOffset, address);
return objects[0];
}
private static long normalize(int value) {
if (value > 0) {
return value;
}
return (~0L >>> 32) & value;
}
public static Object shallowCopy(Object obj) {
long size = sizeOf(obj);//对象所需内存大小
long start = toAddress(obj); //对象的地址起始偏移量
long address = getUnsafe().allocateMemory(size);//分配size大小的内存,返回内存空间地址偏移量,准备放入复制的对象
getUnsafe().copyMemory(start, address, size);//从对象地址起始偏移量开始复制内存到address开始的内存
return fromAddress(address);//将地址转化成对象
}
- 实现多继承,将两个对象的内存空间合并,得到地址转换成对象;
- 动态生成类,动态代理库cglib的原理,使用defineClass方法;
- 抛出异常:unsafe.throwException(new IOException());
- 快速序列化和反序列化:
序列化:使用getLong, getInt, getObject等方法;
反序列化:首先使用allocateInstance生成对象,然后使用putLong, putInt, putObject等方法,填充对象; - 并发操作:CAS(compareAndSwap)的方法包括
compareAndSwapObject(Object obj, long offset, Object expect, Object update);
compareAndSwapInt(Object obj, long offset, int expect, int update);
compareAndSwapLong(Object obj, long offset, long expect, long update);
CAS是原子操作,能够用于实现高性能的线程安全的无锁数据结构,对没错,atomic包内的原子类都是实现的CAS,下面会详细分析。
java一些使用Unsafe的类
- AbstractQueuedSynchronizer即同步器的抽象类,里面实现了线程的阻塞和唤醒:
LockSupport.park(this)和LockSupport.unpark(this)用于阻塞和唤醒线程。 - 如何去使用CAS实现一个线程安全的数据结构(类),直接上代码:
public class AtomicCounter implements Counter{
private volatile long counter = 0;
private Unsafe unsafe;
private long offset;
public AtomicCounter() throws NoSuchFieldException {
unsafe = UnsafeUtils.getUnsafe();
//获得counter的地址偏移量
offset = unsafe.objectFieldOffset(AtomicCounter.class.getDeclaredField("counter"));
}
@Override
public void increment() {
long expect = counter;
while (!unsafe.compareAndSwapLong(this, offset, expect, expect + 1)) {
expect = counter;//此时counter内存值为expect + 1
}
}
@Override
public long getCounter() {
return this.counter;
}
}
很好奇compareAndSwapLong到底做了什么操作,首先分析一下这个方法的参数
compareAndSwapLong(Object obj, long offset, long expect, long update)
从参数表述可以看出,每次修改变量值之前都会比较当前实际值和预期值是否一致,只有一致才会执行值修改,否则do nothing。
注意到counter声明为volatile,这意味着counter的值每次都是在内存中取,再看increment方法,每次修改值之前会设置当前字段值为预期值,并保证在while循环中,counter值每次取都和预期值相同才会执行。
参考
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/