概述
Unsafe
是 sun.misc
包下的一个类,主要提供一些用于执行低级别:
理解对象中的偏移地址
在说明 Unsafe
API 之前,我们先来理解什么是 对象中的偏移地址。
对象在堆中基本内存结构
作为一个 Javaer 都知道对象是存储在堆中的,可是在堆中也肯定由某种数据结构来表示。下图就是 Java 对象在堆中的基本内存结构:
Java 对象在堆中的结构可大致分为三部分,分别是
- 对象头。
- 对象/数组实际数据。
- 对齐填充。
对于 64
位计算机而言,对象头总共占用 16
字节空间。而对于 32
位计算机,对象头总共占用 8
字节。
Unsafe API
// peek 和 poke 操作。这部分是适用于Java堆的对象,并不适用于打包数组元素上。
// They will not work on elements of packed arrays.
// Java 基本数据类型: byte、short、int、long、float、double、boolean、char
/**
* 从给定的Java变量中获取一个值。更具体的说,从给定对象o中以给定偏移量或
* (如果o为空)从数值为给定偏移量的内存地址中获取字段或数组元素。
*
* 除非满足以下情况之一,否则结果将会是不确定的:
* ① 偏移量offset是从某个Java字段的objectFieldOffset获得的,而o所引用的对象是与该字段的类兼容的类。
* ② 偏移量offset和对象引用o(null或非null)分别通过 staticFieldOffset 和 staticFieldBase 从某些
* Java 字段的反射Field表示中获得的。
* ③ 假如o引用的对象是数组,偏移量是公式B+N*S所返回的整数,表示第N个元素偏移量。
* B: 表示arrayBaseOffset()函数求得的值。
* N: 数组的有效索引。
* S: 表示arrayIndexScale()函数求得的值。表示某个数据类型占用多少字节。比如 arrayIndexScale(int[].class)=4。
*
* 如果以上三种情况有一个成立,则该函数调用会执行正确的语义: 该调用引用特定的Java变量(字段或数组元素)。
* 但是,如果该变量实际上并非由对应函数所返回,则结果将是不确定的。
*
* 由于此方法通过两个参数引用一个变量,因此它实际上为 Java 变量提供了一个「双寄存器寻址模式」。
* 当参数o==null时,此方法将其偏移量用作「绝对地址」。与 getInt(long address) 函数相同,它为非 java
* 变量提供单寄存器寻址模式。然而,由于 Java 变量在内存中的布局可能与非Java对象不同,
* 程序员不应假定这两个寻址模式永远是等效的。
* 另外,不能将双寄存器寻址模式的偏移量和单寄存器寻址模式中使用的「长度」混淆。
*
* @param o 驻留在Java堆中的对象
* @param offset 如果o != null,表示驻留在Java堆中的对象的位置。
* 如果o == null,表示绝对地址
*/
public native int getInt(Object o, long offset);
/**
* 将值存储到给定的 Java 变量中。
* 前两个参数可以完全按照 getInt(Object, long) 来理解,表示引用特定的Java变量(字段或数组元素)。
* 将给定的值存储在该变量中。
* 变量必须与方法参数x的类型相同。
*/
public native void putInt(Object o, long offset, int x);
public native boolean getBoolean(Object o, long offset);
public native void putBoolean(Object o, long offset, boolean x);
public native byte getByte(Object o, long offset);
public native void putByte(Object o, long offset, byte x);
public native short getShort(Object o, long offset);
public native void putShort(Object o, long offset, short x);
public native char getChar(Object o, long offset);
public native void putChar(Object o, long offset, char x);
public native long getLong(Object o, long offset);
public native void putLong(Object o, long offset, long x);
public native float getFloat(Object o, long offset);
public native void putFloat(Object o, long offset, float x);
public native double getDouble(Object o, long offset);
public native void putDouble(Object o, long offset, double x);
/**
* 从特定位置获取一个对象
*/
public native Object getObject(Object o, long offset)
/**
* 除非o==null或与参数x字段类型匹配,否则结果将是不确定的。
* If the reference o is non-null,
* car marks or other store barriers for that object (if the VM requires them) are updated.
*/
public native void putObject(Object o, long offset, Object x);
// 下面这些方法处理在C堆中的值
/**
* 从给定的内存地址中获取一个值。
* 如果地址为零,或未指向从allocateMemory获取的块,则结果不确定。
*/
public native byte getByte(long address);
/**
* 将值存储到给定的内存地址中。
* 如果地址为零,或未指向从allocateMemory获取的块,则结果不确定。
*/
public native void putByte(long address, byte x);
public native short getShort(long address);
public native void putShort(long address, short x);
public native char getChar(long address);
public native void putChar(long address, char x);
public native int getInt(long address);
public native void putInt(long address, int x);
public native long getLong(long address);
public native void putLong(long address, long x);
public native float getFloat(long address);
public native void putFloat(long address, float x);
public native double getDouble(long address);
public native void putDouble(long address, double x);
/**
* 从给定的内存地址中获取本地指针。如果地址为0,或未指向从 allocateMemory 获取的内存块,将导致结果不确定。
*/
public native long getAddress(long address);
/**
* 存储一个本地指针到目标内存地址。如果地址为0,或未指向从 allocateMemory 获取的内存块,将导致结果不确定。
* 实际写入的字节数是由 addressSize() 决定的。
*/
public native void putAddress(long address, long x);
// 包装C语言中的malloc、realloc、free函数
/**
* 分配给定大小的新本地内存块(以字节为单位)。
* 存储器的内容未初始化,所以当使用此函数进行内存分配,需要对该内存块进行初始化设置。
* 与freeMemory() 成对出现,否则只管分配不管释放最终会导致堆外内存用尽,程序报OOM异常。
* 也可以通过 reallocateMemory(cap) 对已分配的内存块增大或缩小为 cap 大小。
*/
public native long allocateMemory(long bytes);
/**
* 调整本地内存块的大小。当超过旧块大小的新块内容未初始化,通常是垃圾数据。
* 结果指针对对齐所有值类型。
* 如果参数 address == null,在这种情况下还会继续执行内存分配
*/
public native long allocateMemory(long bytes);
/**
* 将给定内存块中的所有字节设置为一个固定值(通常为0)。
* 这个函数通常用来做初始化用的。
* 此方法通过两个参数确定块的基地址。因此,实际上提供了一个「双寄存器模式」。像 getInt(Object, long) 中所讨论的那样。
* 当参数o==null时,偏移量offset表示的是本地内存的绝对地址。
* 存储以连贯的(原子的)单位表示,其大小由地址和长度参数确定。
* 如果有效地址和长度可以被8整除,则当前内存块表示「long」类型数据。
* 如果有效地址和长度可以被4整除,则当前内存块表示「int」类型数据。
* 如果有效地址和长度可以被2整除,则当前内存场合表示「short」类型数据。
*/
public native void setMemory(Object o, long offset, long bytes, byte value);
public void setMemory(long address, long bytes, byte value) {
setMemory(null, address, bytes, value);
}
/**
* 将给定内存块中的所有字节设置为另一个块的副本。
* 此方法通过两个参数确定每个内存块的基地址,因此,实际上提供了一个「双寄存器模式」。和 getInt(Object, long) 中所讨论的那样。
* 存储以连贯的(原子的)单位表示,其大小由地址和长度参数确定。
* 如果有效地址和长度可以被8整除,则当前内存块表示「long」类型数据。
* 如果有效地址和长度可以被4整除,则当前内存块表示「int」类型数据。
* 如果有效地址和长度可以被2整除,则当前内存场合表示「short」类型数据。
* @param srcBase 源对象
* @param srcOffset 源对象的偏移量
* @param destBase 目标对象
* @param destOffset 目标对象的偏移量
* @param bytes 拷贝长度
*/
public native void copyMemory(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes);
public void copyMemory(long srcAddress, long destAddress, long bytes) {
copyMemory(null, srcAddress, null, destAddress, bytes);
}
/**
* 将所有元素从一个内存复制到另一个内存块。
*「unconditionally」 byte swapping the elements on the fly.
*/
private native void copySwapMemory0(Object srcBase, long srcOffset,
Object destBase, long destOffset,
long bytes, long elemSize);
/**
* 释放由allocatemory、reallocatemory所分配的内存块。
*/
public native void freeMemory(long address);
// 随机查询
// 这个常量不同于 staticFieldOffset, objectFieldOffset 或 arrayBaseOffset 返回的所有结果
public static final int INVALID_FIELD_OFFSET = -1;
/**
* 获取给定字段在其类的存储分配中的位置。不要期望对这个偏移量执行任何类型的算术运算。
* 它只是一个传递给不安全堆内存访问器的cookie。
* 任何给定的字段将始终具有相同的偏移量和基数,同一类中任何两个不同的字段都不会具有相同的偏移量和基数。
*/
public native long staticFieldOffset(Field f);
/**
* 和 staticFieldBase 一起获取静态字段的位置。
*/
public native long objectFieldOffset(Field f);
public native Object staticFieldBase(Field f);
/**
* 检测给定的类是否需要初始化。这通常需要与获取类的静态字段基一起使用。
* @return false only if a call to {@code ensureClassInitialized} would have no effect
*/
public native boolean shouldBeInitialized(Class<?> c);
/**
* 确保给定的类已经初始化。这通常需要与获取类的静态字段基一起使用。
*/
public native void ensureClassInitialized(Class<?> c);
/**
* 获取给定数组类型的存储分配中用于寻址元素的缩放比例因子。
* 通常"narrow"类型的数组不能使用比如 getByte() 获取
*/
public native int arrayBaseOffset(Class<?> arrayClass);
public static final int ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
public static final int ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
public static final int ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
public static final int ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
public static final int ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
public static final int ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
public static final int ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
public static final int ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
public static final int ARRAY_OBJECT_BASE_OFFSET = = theUnsafe.arrayBaseOffset(Object[].class);
/**
* 获取给定数组类的存储分配中用于寻址元素的比例因子
*/
public native int arrayIndexScale(Class<?> arrayClass);
public static final int ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
public static final int ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
public static final int ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
public static final int ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
public static final int ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
public static final int ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
public static final int ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
public static final int ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
public static final int ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
/**
*
*
*
*
*/
/**
* 返回通过putAddress()存储的本地指针的大小(以字节为单位),
* 这个值可能是4或8。
* 注意在其他基本类型的大小完全由它们的信息内存所决定的
*/
public native int addressSize();
public static final int ADDRESS_SIZE = theUnsafe.addressSize();
/**
* 本地内存页的大小
*/
public native int pageSize();
// random trusted operations from JNI:
/**
* 定义一个类,该类是完全可信的,不需要进行安全检查
*/
public native Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);
/**
* 定义一个匿名类,该类不会被类加载器或系统字典所知道。
*/
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
/**
* 实例化对象,但不需要经过任务构造器。
* 如果该类还没有被初始化,则初始化该类
*/
public native Object allocateInstance(Class<?> cls)
throws InstantiationException;
// 与锁相关
@Deprecated
public native void monitorEnter(Object o);
@Deprecated
public native void monitorExit(Object o);
@Deprecated
public native boolean tryMonitorEnter(Object o);
public native void throwException(Throwable ee);
/**
* 「CAS」如果与期望值相等,则将对应属性更新为目标值
* @param o 目标对象
* @param offset 偏移量
* @param expected 期望值
* @param x 目标值
* @return
*/
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
/**
* 「CAS」如果与期望值相等,则将对应属性更新为目标值
* @param o 目标对象
* @param offset 偏移量
* @param expected 期望值
* @param x 目标值
* @return
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
/**
* 「CAS」如果与期望值相等,则将对应属性更新为目标值
* @param o 目标对象
* @param offset 偏移量
* @param expected 期望值
* @param x 目标值
* @return
*/
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);
/**
* 使用「volatile load」语义从给定的Java变量中获取引用值
* 否则与getObject(Object, long)相同
*/
public native Object getObjectVolatile(Object o, long offset);
/**
* 使用「volatile store」语义设置数据
* 否则与 putObject(Object, long, Object)相同
*/
public native void putObjectVolatile(Object o, long offset, Object x);
public native int getIntVolatile(Object o, long offset);
public native void putIntVolatile(Object o, long offset, int x);
public native boolean getBooleanVolatile(Object o, long offset);
public native void putBooleanVolatile(Object o, long offset, boolean x);
public native byte getByteVolatile(Object o, long offset);
public native void putByteVolatile(Object o, long offset, byte x);
public native short getShortVolatile(Object o, long offset);
public native void putShortVolatile(Object o, long offset, short x);
public native char getCharVolatile(Object o, long offset);
public native void putCharVolatile(Object o, long offset, char x);
public native long getLongVolatile(ObjectputOrderedObject
o, long offset);
public native void putLongVolatile(Object o, long offset, long x);
public native float getFloatVolatile(Object o, long offset);putOrderedObject
public native void putFloatVolatile(Object o, long offset, float x);
public native double getDoubleVolatile(Object o, long offset);
public native void putDoubleVolatile(Object o, long offset, double x);
/**
* 不保证存储值对其他线程立即可见,属于 putObjectVolatile(Object, long, Object) 版本。
* 在被volaile修饰的字段(或数组单元格,否则只能傅volatile访问)情况下才有用
*/
public native void putOrderedObject(Object o, long offset, Object x);
/** Ordered/Lazy version of {@link #putIntVolatile(Object, long, int)} */
public native void putOrderedInt(Object o, long offset, int x);
/** Ordered/Lazy version of {@link #putLongVolatile(Object, long, long)} */
public native void putOrderedLong(Object o, long offset, long x);
/**
* 解除 对park上阻塞的 线程阻塞(Unblock the given thread blocked on park)
* 如果线程没有阻塞,则后续的调用也不阻塞。
* 注意: 这个操作「不安全」,仅仅是因为调用都必须以某种方式确保线程没有被销毁。
* 通常不需要特别的东西来确保这一点,但当通过「native」方法调用时,这几乎不是自动的。
*/
public native void unpark(Object thread);
/**
* 阻塞当前线程,直到
* ① a balancing unpark occurs
* ② a balancing unpark has already occurred
* ③ 线程中断
* ④ isAbsolute=false&time>0,则时间单位为纳秒,经过time纳秒后返回。
* ⑤ isAbsolute=true&time>0,则时间单位为毫秒。经过time毫秒后返回。
* ⑥ 未知原因错误直接返回
* @param isAbsolute 是否为绝对时间
* @param time 等待时间值
*/
public native void park(boolean isAbsolute, long time);
/**
* 获取分配给可用处理器的系统运行队列中在不同时间段的平均负载。
*/
public native int getLoadAverage(double[] loadavg, int nelems);
// 非native的 CAS 实现
/**
* 「CAS」原子地将给定值添加到给定对象o的偏移量offset处(字段或数组元素)
* @param o 目标对象
* @param offset 字段或元素偏移量
* @param delta 被加数
* @return 旧值
* @since 1.8
*/
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// #1 获取旧值
v = getIntVolatile(o, offset);
// #2 通过「CAS」对于旧值是否可以更新,如果不可以,继续循环
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
public final long getAndSetLong(Object o, long offset, long newValue) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, newValue));
return v;
}
public final Object getAndSetObject(Object o, long offset, Object newValue) {
Object v;
do {
v = getObjectVolatile(o, offset);
} while (!compareAndSwapObject(o, offset, v, newValue));
return v;
}
/**
* 「store」将处理器缓存的数据刷新到内存中。
* 「load」将内存存储的数据拷贝到处理器的缓存中。
* 「load_load_barriers」确保load1的数据装载先于load2及其后所有装载指令的操作。
* 「store_store_barriers」确保store1立刻刷新数据到内存(使其对其他处理器可见)的操作等于store2及其后所有存储指令的操作。
* 「load_store_barriers」确保load1的数据装载等于store2及其后所有的存储指令刷新数据到内存的操作。
* 「store_load_barriers」该屏障确保store1立刻刷新数据到内存的操作先于load2及其后所有装载指令的操作。
* 它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令。
* 这一道内存屏障也称「全能屏障」
*/
// Ensures lack of reordering of loads before the fence with loads or stores after the fence.
public native void loadFence();
/**
* Ensures lack of reordering of stores before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void storeFence();
/**
* Ensures lack of reordering of loads or stores before the fence
* with loads or stores after the fence.
* @since 1.8
*/
public native void fullFence();
/**
* Throws IllegalAccessError; for use by the VM.
* @since 1.8
*/
private static void throwIllegalAccessError() {
throw new IllegalAccessError();
}
/**
* 「big-endian」 本机字节顺序为「大端」,返回true
* 「little-endian」 本机字节顺序为「小端」,返回false
*/
public final boolean isBigEndian() { return BE; }
/**
* @return Returns true if this platform is capable of performing
* accesses at addresses which are not aligned for the type of the
* primitive type being accessed, false ocompareAndSwapObject
therwise.
*/
public final boolean unalignedAccess() { return unalignedAccess; }
// JVM interface methods
private native boolean unalignedAccess0();
private native boolean isBigEndian0();
// BE is true iff the native endianness of this platform is big.
private static final boolean BE = theUnsafe.isBigEndian0();
// unalignedAccess is true iff this platform can perform unaligned accesses.
private static final boolean unalignedAccess = theUnsafe.unalignedAccess0();
putOrderedObject
/**
* 向目标对象x偏移量为offset处写入对象o的引用,
* 内存立即可见(volatile 保证)
*/
public native void putObjectVolatile(Object o, long offset, Object x);
/**
* 向目标对象o偏移量为offset处写入对象x的引用,
* 有序、延迟可见,即不能保证其他线程立即看到,性能比putObjectVolatile要高
* 这个方法能使用store-store屏障,性能损耗较小,而volatile使用store-load屏障,性能损耗高。
*/
public native void putOrderedObject(Object o, long offset, Object x);
首先将 volatile 变量转为普通变量提升性能,因为在 put 中需要读取到最新的数据,因此接下来调用 UNSAFE.getObjectVolatile 获取到最新的头结点,但是通过调用 UNSAFE.putOrderedObject 让变量写入主存的时间延迟到 put 方法的结尾,一来缩小临界区提升性能,而来也能保证其他线程读取到的是完整数据。
Unsafe 底层原理
JVM 源码位于 unsafe.cpp
上:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
从源码可见,它是通过 Atomic::cmpxchg
实现比较和替换操作。参数 x 是目标值,参数 e 是旧值,addr 为目标地址。
而更底层 Linux 操作系统实现如下:
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;
}