1、简介
Unsafe
类是在 sun.misc
包中,使 Java 拥有了类似 C 语言指针操作内存的功能。
该类使用了单例模式,需要通过静态方法 getUnsafe()
来获取 theUnsafe
实例。
如果不是由 Java 启动类加载器加载的类调用 getUnsafe()
方法,会抛出 SecurityException
异常,源码如下:
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
开发者可以使用 Java 反射技术来获取 theUnsafe
实例,代码如下:
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
}
2、方法介绍
2.1、操作内存
2.1.1、获取内存页大小
public native int pageSize();
pageSize() 方法返回操作系统内存页的字节数,例如 4096。
2.1.2、分配内存
public native long allocateMemory(long size);
allocateMemory()
方法分配一块参数 size
指定大小的内存空间,这块内存块可能包含有垃圾数据(没有初始化清零)。如果分配失败则抛出 java.lang.OutOfMemoryError
。该方法返回一个非 0 的内存地址。
2.1.3、重新分配内存
public native long reallocateMemory(long address, long size);
reallocateMemory()
方法重新分配一块参数 size
指定大小的内存空间,将数据从参数 address
指定的内存块拷贝到新分配的内存块。如果参数 address
的值为 0 则和 allocateMemory()
方法相同。
2.1.4、设置内存
public native void setMemory(Object obj, long offset, long bytes, byte value);
setMemory()
方法将参数 obj
、offset
指定地址开始的 bytes
个字节的内存块的值设置为参数 value
,一般用于分配内存后对内存块进行初始化清零。
public class User {
// 声明为long类型而不能是Long类型
protected long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
Field idField = User.class.getDeclaredField("id");
long idFiledOffset = unsafe.objectFieldOffset(idField);
User user = new User();
user.setId(0x0102030405060708L);
unsafe.setMemory(user, idFiledOffset, 5, (byte) 1);
// Java字节序为ByteOrder.LITTLE_ENDIAN,低位在前
// 输出:01 02 03 01 01 01 01 01
System.out.println("user.getId()===" + user.getId());
2.1.5、读取和写入内存
- getXXX(long address) 方法
- putXXX(long address, XXX value) 方法
XXX 包括 8 种基本类型 boolean、byte、short、char、int、long、float、double 和引用类型 Object。
// 获取指针长度?值为4或8
public native int addressSize();
public native long getAddress(long address);
public native void putAddress(long address, long v);
// long
public native long getLong(long address);
public native void putLong(long address, long v);
2.1.6、拷贝内存
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
2.1.7、释放内存
public native void freeMemory(long address);
freeMemory()
方法释放 allocateMemory()
和 reallocateMemory()
方法分配的内存空间。如果参数 address
为 0 则什么也不做。
2.1.8、示例代码 DirectByteBuffer
DirectByteBuffer(int cap) {
// int mark, int pos, int lim, int cap
super(-1, 0, cap, cap);
// 检查JVM是否需要对堆外内存进行对齐
boolean pa = VM.isDirectMemoryPageAligned();
// 获取内存页大小
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
// 检查已使用堆外内存容量大小totalCapacity是否超过限制-XX:MaxDirectMemorySize
// CAS设置totalCapacity、reservedMemory
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 1.分配内存
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
// 如果内存分配失败,恢复分配内存前的totalCapacity、reservedMemory
Bits.unreserveMemory(size, cap);
// 抛出OutOfMemoryError
throw x;
}
// 2.初始化内存清零
unsafe.setMemory(base, size, (byte) 0);
// 3.内存对齐
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 4.创建内存释放任务Cleaner和Deallocator
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
private static class Deallocator implements Runnable {
public void run() {
if (address == 0) {
// Paranoia
return;
}
// 释放内存
unsafe.freeMemory(address);
address = 0;
// 恢复分配内存前的totalCapacity、reservedMemory
Bits.unreserveMemory(size, capacity);
}
}
2.2、内存屏障
public native void loadFence();
public native void storeFence();
public native void fullFence();
2.3、操作类
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
public native boolean shouldBeInitialized(Class<?> cls);
public native void ensureClassInitialized(Class<?> cls);
2.4、操作对象
2.4.1、实例化对象
public native Object allocateInstance(Class<?> var1) throws InstantiationException;
allocateInstance()
方法在实例化对象的时候,会触发类的加载和初始化,但是并不会调用构造方法。常用于反序列化过程中,既保证安全又可以节省时间。
User user = (User) unsafe.allocateInstance(User.class);
System.out.println(user);
// 输出结果:
// this is static block
// User{id=0, name='null'}
@Data
public class User {
static int STATIC_INT;
static {
STATIC_INT = 10;
System.out.println("this is static block");
}
protected long id;
private String name;
private User() {
System.out.println("this is private constructor without arg");
}
public User(long id, String name) {
this.id = id;
this.name = name;
System.out.println("this is constructor with args");
}
}
2.4.2、获取属性的偏移
// 获取静态属性的Object,即Class对象
public native Object staticFieldBase(Field var1);
// 获取静态属性的偏移
public native long staticFieldOffset(Field var1);
// 获取非静态属性的偏移
public native long objectFieldOffset(Field var1);
示例代码:
// 输出对象布局
System.out.println(ClassLayout.parseInstance(user).toPrintable());
// 输出属性偏移
for (Field f : User.class.getDeclaredFields()) {
if ((Modifier.STATIC & f.getModifiers()) != 0) {
System.out.println(f.getName()
+ " 属性对应的内存地址偏移 : "
+ unsafe.staticFieldOffset(f));
} else {
System.out.println(f.getName()
+ " 属性对应的内存地址偏移 : "
+ unsafe.objectFieldOffset(f));
}
}
2.4.2.1、指针压缩
开启对象指针压缩 -XX:-UseCompressedClassPointers
开启类型指针压缩 -XX:+UseCompressedOops
对象头占用 8 字节 Mark Word + 4 字节 Class Pointer = 12 字节
实例属性占用 4 字节 String 类型 + 8 字节 long 类型 = 12 字节
对齐填充占用 0 字节
org.example.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 java.lang.String User.name null
16 8 long User.id 0
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
STATIC_INT 属性对应的内存地址偏移 : 104
id 属性对应的内存地址偏移 : 16
name 属性对应的内存地址偏移 : 12
开启对象指针压缩 -XX:-UseCompressedClassPointers
关闭类型指针压缩 -XX:+UseCompressedOops
对象头占用 8 字节 Mark Word + 8 字节 Class Pointer = 16 字节
实例属性占用 8 字节 long 类型 + 4 字节 String 类型 = 12 字节
对齐填充占用 4 字节
org.example.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) b0 45 7d 9d (10110000 01000101 01111101 10011101) (-1652734544)
12 4 (object header) 7a 02 00 00 (01111010 00000010 00000000 00000000) (634)
16 8 long User.id 0
24 4 java.lang.String User.name null
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
STATIC_INT 属性对应的内存地址偏移 : 104
id 属性对应的内存地址偏移 : 16
name 属性对应的内存地址偏移 : 24
关闭对象指针压缩 -XX:-UseCompressedOops
关闭类型指针压缩 -XX:-UseCompressedClassPointers
对象头占用 8 字节 Mark Word + 8 字节 Class Pointer = 16 字节
实例属性占用 8 字节 long 类型 + 8 字节 String 类型 = 16 字节
对齐填充占用 0 字节
org.example.User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) b0 45 a4 ca (10110000 01000101 10100100 11001010) (-895203920)
12 4 (object header) a5 02 00 00 (10100101 00000010 00000000 00000000) (677)
16 8 long User.id 0
24 8 java.lang.String User.name null
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
STATIC_INT 属性对应的内存地址偏移 : 160
id 属性对应的内存地址偏移 : 16
name 属性对应的内存地址偏移 : 24
2.4.3、读取和写入对象属性
2.4.3.1、getXXX/putXXX
- getXXX(Object obj, long offset)
- putXXX(Object obj, long offset, XXX value)
XXX 包括 8 种基本类型 boolean、byte、short、char、int、long、float、double 和引用类型 Object。
- 对于静态属性,参数
obj
可以通过staticFieldBase()
方法获取,参数offset
可以通过staticFieldOffset()
方法获取,也可以直接通过User.class
获取。 - 对于非静态属性,参数
obj
需要开发者自己创建,参数offset
可以通过objectFieldOffset()
方法获取。
// Object
public native Object getObject(Object obj, long offset) {
public native void putObject(Object obj, long offset, Object v);
// long
public native long getLong(Object obj, long offset);
public native void putLong(Object obj, long offset, long v);
2.4.3.1、getXXXVolatile/putXXXVolatile
- getXXXVolatile(Object obj, long offset)
- putXXXVolatile(Object obj, long offset, XXX value)
XXX 包括 8 种基本类型 boolean、byte、short、char、int、long、float、double 和引用类型 Object。
// Object
public native Object getObjectVolatile(Object obj, long offset);
public native void putObjectVolatile(Object obj, long offset, Object v);
// long
public native long getLongVolatile(Object obj, long offset);
public native void putLongVolatile(Object obj, long offset, long v);
引自 openjdk\src\hotspot\share\oops\access.hpp
MO_SEQ_CST: Sequentially consistent stores.
- The stores are observed in the same order by MO_SEQ_CST loads on other processors
- Preceding loads and stores in program order are not reordered with subsequent loads and stores in program order
- Guarantees from releasing stores hold
// putXXX
oop x = JNIHandles::resolve(x_h);
oop p = JNIHandles::resolve(obj);
assert_field_offset_sane(p, offset);
HeapAccess<ON_UNKNOWN_OOP_REF>::oop_store_at(p, offset, x);
// putXXXVolatile
oop x = JNIHandles::resolve(x_h);
oop p = JNIHandles::resolve(obj);
assert_field_offset_sane(p, offset);
HeapAccess<MO_SEQ_CST | ON_UNKNOWN_OOP_REF>::oop_store_at(p, offset, x);
2.4.3.2、putOrderedXXX
putOrderedXXX() 方法,XXX 包括基本类型 int、long 和引用类型 Object。
public native void putOrderedObject(Object obj, long offset, Object x);
public native void putOrderedInt(Object obj, long offset, int x);
public native void putOrderedLong(Object obj, long offset, long x);
2.5、操作数组
2.5.1、获取数组基本偏移
public native int arrayBaseOffset(Class arrayClass);
arrayBaseOffset()
方法获取给定数组中第 1 个元素的内存地址偏移量。
2.5.2、获取数组比例因子
public native int arrayIndexScale(Class arrayClass);
Reports the scale factor for addressing elements in the storage allocation of a given array class. However, arrays of “narrow” types will generally not work properly with accessors like {@link #getByte(Object, long)}, so the scale factor for such classes is reported as zero.
arrayIndexScale()
方法获取给定数组中每个元素内存地址的比例因子,获取不到则返回 0。
// indexScale=8
int indexScale = unsafe.arrayIndexScale(long[].class);
// indexScale=4
int indexScale = unsafe.arrayIndexScale(int[].class);
arrayBaseOffset()
方法配合 arrayIndexScale()
方法使用可以获取数组中每个元素在内存中的位置。
2.5.3、示例代码 ConcurrentHashMap
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
private static final sun.misc.Unsafe U;
private static final long ABASE;
private static final int ASHIFT;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
// ...
Class<?> ak = Node[].class;
ABASE = U.arrayBaseOffset(ak);
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
static final <K,V> boolean casTabAt(Node<K,V>[] tab,
int i,
Node<K,V> c,
Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
}
2.6、操作异常
/** Throws the exception without telling the verifier. */
public native void throwException(Throwable e);
2.7、操作线程
2.7.1、阻塞线程
public native void park(boolean isAbsolute, long time);
park()
方法阻塞当前线程,直到
unpark()
方法被调用- 线程被中断
- 参数
time
指定时间超时
等条件出现。
JVM 采用 pthread_cond_wait 或 pthread_cond_timedwait 实现。
参数 isAbsolute
如果为 true 则表示参数 time 采用绝对时间,代表最后期限的毫秒数时间戳;否则采用相对时间,代表经过的纳秒数。
如果调用 unpark()
方法后紧接着调用 park()
方法,将不会阻塞线程。
2.7.2、唤醒线程
public native void unpark(Object thread);
unpark()
方法可以唤醒指定线程。JVM 采用 pthread_cond_signal 实现。
整个并发框架中,除了
- JDK1.5 引入的 Exchanger
- JDK1.7 引入的 ForkJoinPool
- JDK1.8 引入的 StampedLock
对线程的阻塞和唤醒操作被封装在 LockSupport
类中,LockSupport
类中有各种重载的 park()
方法,但最终都调用了 UNSAFE.park()
方法。
2.8、CAS操作
public final Object getAndSetObject(Object obj, long offset, Object newValue)
public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update)
public final int getAndSetInt(Object obj, long offset, int newValue)
public final int getAndAddInt(Object obj, long offset, int delta)
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update)
public final long getAndSetLong(Object obj, long offset, long newValue)
public final long getAndAddLong(Object obj, long offset, long delta)
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update)
参数 | 类型 | 作用 |
---|---|---|
obj | Object | 待操作的对象 |
offset | long | 待更新字段的内存地址偏移量 |
expect | Object/int/long | 待更新字段的期望值 |
update | Object/int/long | 如果期望值和待更新字段的实际值一致,则将待更新字段的内存值更新为 update |