关于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; } } ``` 我们注意到这个方法是调用者敏感的,内部会检查该CallerClass是不是由系统类加载器`BootstrapClassLoader`加载。由系统类加载器加载的类调用`getClassLoader()`会返回null,所以要检查类是否为bootstrap加载器加载只需要检查该方法是不是返回null。 所以想直接获取,必须调用者由系统类加载器加载,即启动参数加上`-Xbootclasspath/p:xxxx`。 关于详细的启动参数可以参考这个链接[Launches a Java application](http://docs.oracle.com/javase/7/docs/technotes/tools/windows/java.html)。
-
反射
Unsafe.getUnsafe() Field theUnsafe = null; Unsafe unsafe = null; try { theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); } catch (NoSuchFieldException e) { e.printStackTrace(); } theUnsafe.setAccessible(true); try { unsafe = (Unsafe) theUnsafe.get(null); } catch (IllegalAccessException e) { e.printStackTrace(); } ``` 得一提的是,反射获取`static`成员的时候参数是`null`。因为static成员不依附于实例,这在上一篇博客[深入Java方法调用](http://www.jianshu.com/p/c8928d98cb8e)中已经提到过了。
通过Unsafe我们能做些什么
-
创建对象
有时候会需要动态地创建一些对象,但是对应的类没有无参构造器。Objenesis就是一个做这种事情的库,它对于不同的平台有不同的策略,其中UnsafeFactoryInstantiator
这一个就是用Unsafe.allocateInstance
来创建对象的。Spring中就用到了这个库
CGLIB-based proxy classes no longer require a default constructor. Support is provided via the objenesis library which is repackaged inline and distributed as part of the Spring Framework. With this strategy, no constructor at all is being invoked for proxy instances anymore.
3. New Features and Enhancements in Spring Framework 4.0
- 访问对象成员
对应的方法是getXXX
,putXXX
,XXX对应不同的类型。
方法接受两个参数,第一个为字段所属对象,第二个为偏移量。
字段的偏移量可以通过fieldOffset(Field)
获取,其中static字段的第一个参数应该为对应的Class,而不是实例对象。
public class Test {
static Unsafe unsafe ;
static int i = 1;
static {
Field theUnsafe = null;
try {
theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
theUnsafe.setAccessible(true);
try {
unsafe = (Unsafe) theUnsafe.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private Test() {
}
public static void main(String[] args){
try {
long i = unsafe.staticFieldOffset(Test.class.getDeclaredField("i"));
System.out.println(unsafe.getInt(new Test(), i));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
再提一个无关的事情,我在很多地方看到说类的Static变量是存在方法区中的,而据我观察并不是这样。
比如访问类变量,unsafe.get接收的第一个参数是Class对象,而这个对象并不是方法区中的InstanceKlass,所以我猜Static变量应该是接在Class对象的尾部。
我们用jol来打印Class对象,(这个库其实也是用Unsafe+反射实现的)
import org.openjdk.jol.info.ClassLayout;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class Test {
static Unsafe unsafe;
static {
Field f = null;
try {
f = Unsafe.class.getDeclaredField("theUnsafe");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
f.setAccessible(true);
try {
unsafe = (Unsafe) f.get(null);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws NoSuchFieldException {
System.out.println(ClassLayout.parseClass(Class.class).toPrintable());
System.out.println(unsafe.staticFieldOffset(Test.class.getDeclaredField("unsafe")));
}
}
输出:
java.lang.Class object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 Constructor Class.cachedConstructor N/A
16 4 Class Class.newInstanceCallerCache N/A
20 4 String Class.name N/A
24 4 (alignment/padding gap) N/A
28 4 SoftReference Class.reflectionData N/A
32 4 ClassRepository Class.genericInfo N/A
36 4 Object[] Class.enumConstants N/A
40 4 Map Class.enumConstantDirectory N/A
44 4 AnnotationData Class.annotationData N/A
48 4 AnnotationType Class.annotationType N/A
52 4 ClassValueMap Class.classValueMap N/A
56 32 (alignment/padding gap) N/A
88 4 int Class.classRedefinedCount N/A
92 4 (loss due to the next object alignment)
Instance size: 96 bytes
Space losses: 36 bytes internal + 4 bytes external = 40 bytes total
104
Class对象的大小为96, 和 104非常接近,如果运行的时候关闭压缩指针-XX:-UseCompressedOops
,输出:
Instance size: 152 bytes
Space losses: 48 bytes internal + 4 bytes external = 52 bytes total
160
也非常接近,而且两种情况第一个字段的偏移量和Class的长度都是相差8个字节,刚好是64位的一个字长。那么这中间的空档应该是instanceKlass的指针。
我们看到openjdk里面的javaClasses.cpp
,这里 是一个在线版本。
Klass* java_lang_Class::as_Klass(oop java_class) {
//%note memory_2
assert(java_lang_Class::is_instance(java_class), "must be a Class object");
Klass* k = ((Klass*)java_class->metadata_field(_klass_offset));
assert(k == NULL || k->is_klass(), "type check");
return k;
}
这个方法是将Class对象转换成instanceKlass,可以看到instanceClass确实在Class对象的一定偏移量上(具体是多少不知道了)。那我认为他直接接在Class对象的尾部应该是合理的,而再后面就是类变量。(最起码jdk8是这样)
set put 还有对应的volatile,ordered版本。volatile对应volatile读写,很好理解。比如数组元素是没有办法声明为volatile的,你只能声明数组的引用是volatile,然后通过数组对象加偏移量调用这个方法来volatile读写数组元素 。而ordered版本对应Atomic包中的lazyset方法,参见注释(jdk src里面是没注释的,只能看openjdk里面的)。
Version of {@link #putObjectVolatile(Object, long, Object)} that does not guarantee immediate visibility of the store to other threads. This method is generally only useful if the underlying field is a Java volatile (or if an array cell, one that is otherwise only accessed using volatile accesses).
而看到Unsafe native的实现
// The non-intrinsified versions of setOrdered just use setVolatile
非常坑爹,这里面setOrdered和setVolatile是一模一样的,也就没办法知道他到底怎么搞的。
这两个东西和ConcurrentHashMap实现相关,但是我暂时记不起来了,空下来再补充。
-
并发相关
上面的东西已经和并发有点关联,所以就接着讲了。
比如cas操作compareAndSwapXXX,对应AtomicXXX里面的compareAndSet,
monitorEnter和monitorEnter对应synchronize块的进入和退出的加锁解锁,而参数就是锁对象。在jdk8中Unsafe多了几个东西,是之前没有的。loadFence,storeFence,fullFence,这三个东西对应的native方法是OrderAccess的包装。linux_x86的实现:点这里
inline void OrderAccess::acquire() {
volatile intptr_t local_dummy;
#ifdef AMD64
__asm__ volatile ("movq 0(%%rsp), %0" : "=r" (local_dummy) : : "memory");
#else
__asm__ volatile ("movl 0(%%esp),%0" : "=r" (local_dummy) : : "memory");
#endif // AMD64
}
inline void OrderAccess::release() {
// Avoid hitting the same cache-line from
// different threads.
volatile jint local_dummy = 0;
}
inline void OrderAccess::fence() {
if (os::is_MP()) {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
__asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
__asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
}
}
这里可以找到和四种内存屏障的对应:Memory Barriers
inline void OrderAccess::loadload() { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore() { acquire(); }
inline void OrderAccess::storeload() { fence(); }
前面的unsafe.putVolatile的实现也在这里面,
#define GET_FIELD_VOLATILE(obj, offset, type_name, v) \
oop p = JNIHandles::resolve(obj); \
if (support_IRIW_for_not_multiple_copy_atomic_cpu) { \
OrderAccess::fence(); \
} \
volatile type_name v = OrderAccess::load_acquire((volatile type_name*)index_oop_from_field_offset_long(p, offset));
#define SET_FIELD_VOLATILE(obj, offset, type_name, x) \
oop p = JNIHandles::resolve(obj); \
OrderAccess::release_store_fence((volatile type_name*)index_oop_from_field_offset_long(p, offset), truncate_##type_name(x));
volatile读:
inline jint OrderAccess::load_acquire(volatile jint* p) { return *p; }
参数加上了volatile修饰符,直接返回。
volatile写:
inline void OrderAccess::release_store_fence(volatile jint* p, jint v) {
__asm__ volatile ( "xchgl (%2),%0"
: "=r" (v)
: "0" (v), "r" (p)
: "memory");
}
查一下资料:
XCHG exchanges two operands. The operands can be in either order. If a memory operand is involved, BUS LOCK is asserted for the duration of the exchange, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL.
总线锁和上面的LOCK指令相关
The LOCK# signal is asserted when there is a locked memory access due to uncacheable memory, locked operation that spans two cache lines, and page-walk from an uncacheable page table.
cas操作: cmpchg
指令。
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
- 分配/释放内存
allocateMemory/freeMemory
DirectByteBuffer就是用的这个分配和释放堆外内存的。对应malloc和free,就没什么好看的了。