基于jdk1.8,sun.misc.Unsafe,jdk17 实现可能会有所不同。
获取实例
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at com.example.common.juc.unsafe.Client.main(Client.java:15)
由于不是系统的类加载器所以报错,安全异常。所以我们无法通过正向的方式来获取实例,只能通过反射。
通过反射获取实例
public class UnsafeUtil {
public static Unsafe get() throws Exception {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
}
}
public class Client {
public static void main(String[] args) throws Exception {
Unsafe unsafe = UnsafeUtil.get();
System.out.println(unsafe);
}
}
获取字段偏移量 offset
定义一个简单类
public class UnsafePojo {
String name;
int age;
String addr;
static int staticAge;
//省略get set tostring
}
获取staticFieldOffset和objectFieldOffset
// get offset
Field[] fields = UnsafePojo.class.getDeclaredFields();
for (Field field : fields) {
if (field.getName().equals("staticAge")) {
long offset = unsafe.staticFieldOffset(field);
System.out.println(field.getName() + ":" + offset);
continue;
}
long offset = unsafe.objectFieldOffset(field);
System.out.println(field.getName() + ":" + offset);
}
结果
name:16
age:12
addr:20
staticAge:104
后面需要传入偏移的时候通过这个例子中的方法获取即可。
CAS(compare and swap)
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
compareAndSwapInt
UnsafePojo pojo = new UnsafePojo();
pojo.setAge(10);
boolean b1 = unsafe.compareAndSwapInt(pojo, 12, 12, 15);
System.out.println(pojo.getAge());
System.out.println(b1);
或者
unsafe.compareAndSwapInt(pojo, unsafe.objectFieldOffset(UnsafePojo.class.getDeclaredField("age")), 10, 15);
compareAndSwapLong
添加一段代码
//cas obj
boolean b2 = unsafe.compareAndSwapLong(pojo, 12, 10, 15);
System.out.println(pojo.getAge());
System.out.println(b2);
输出
15
true
compareAndSwapObject
添加一段代码
pojo.setName("abc");
boolean b3 = unsafe.compareAndSwapObject(pojo,
unsafe.objectFieldOffset(UnsafePojo.class.getDeclaredField("name")), "abc", "abcd");
System.out.println(b3);
System.out.println(pojo.getName());
输出
true
abcd
CAS c++源码
源码位置 openjdk\hotspot\src\share\vm\prims\unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapObject(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject e_h, jobject x_h))
UnsafeWrapper("Unsafe_CompareAndSwapObject");
oop x = JNIHandles::resolve(x_h);
oop e = JNIHandles::resolve(e_h);
oop p = JNIHandles::resolve(obj);
HeapWord* addr = (HeapWord *)index_oop_from_field_offset_long(p, offset);
oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e, true);
jboolean success = (res == e);
if (success)
update_barrier_set((void*)addr, x);
return success;
UNSAFE_END
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
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj,
jlong offset, jlong e, jlong x))
UnsafeWrapper("Unsafe_CompareAndSwapLong");
Handle p (THREAD, JNIHandles::resolve(obj));
jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
if (VM_Version::supports_cx8())
return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
else {
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) { *addr = x; success = true; }
return success;
}
UNSAFE_END
这三段代码均调用了 index_oop_from_field_offset_long 这一个方法。
源码用到了 cmpxchg 这一条指令,当执行 cmpxchg 汇编指令的时候,cpu 会锁定总线,总而保证命令执行的一个原子性。
CAS 常见问题:ABA,数据库中常采用 version 版本号来解决。
当竞争比较激烈的时候,cpu 会处于一个忙等待的状态。
原子操作
i++的字节码如下
0: iconst_0
1: istore_1
2: iinc 1, 1
源码
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
public final long getAndSetLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var4));
return var6;
}
public final Object getAndSetObject(Object var1, long var2, Object var4) {
Object var5;
do {
var5 = this.getObjectVolatile(var1, var2);
} while(!this.compareAndSwapObject(var1, var2, var5, var4));
return var5;
}
用法:三个参数,第一个是对象,第二个是偏移,第三个是需要加的值。
getAndAddInt
System.out.println("getAndAddInt...");
System.out.println(pojo.getAge());
int age = unsafe.getAndAddInt(pojo,
unsafe.objectFieldOffset(UnsafePojo.class.getDeclaredField("age")), 5);
System.out.println(age);
System.out.println(pojo.getAge());
输出
getAndAddInt...
15
15
20
返回的是操作之前的数值也就是初始值。
getAndSetObject
//get and set
System.out.println("getAndSetObject...");
pojo.setAddr("addr 1");
System.out.println(pojo.getAddr());
String addr = (String)unsafe.getAndSetObject(pojo,
unsafe.objectFieldOffset(UnsafePojo.class.getDeclaredField("addr")), "addr 2");
System.out.println(addr);
System.out.println(pojo.getAddr());
输出
getAndSetObject...
addr 1
addr 1
addr 2
原子操作总结
getAndAddxxx是增加值,getAndSetxxx是设置值,都利用上面提到的CAS保证了原子性。
线程调度
等待和唤醒
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
过时方法
@Deprecated
public native void monitorEnter(Object var1);
/** @deprecated */
@Deprecated
public native void monitorExit(Object var1);
/** @deprecated */
@Deprecated
public native boolean tryMonitorEnter(Object var1);
park 的单位是纳秒。当前线程睡眠3s的写法如下。
u.park(false, TimeUnit.SECONDS.toNanos(3));
park的第一个参数是 isAbsolute,为true代表绝对时间,false代表相对时间。
public static void main(String[] args) throws Exception {
Unsafe u = UnsafeUtil.get();
Thread t1 = new Thread(() -> {
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + ":start");
u.park(false, TimeUnit.SECONDS.toNanos(3));
System.out.println(System.currentTimeMillis() + ":" + Thread.currentThread().getName() + ":end");
}, "t1");
t1.start();
System.out.println(System.currentTimeMillis() + ":st");
TimeUnit.SECONDS.sleep(2);
System.out.println(System.currentTimeMillis() + ":et");
u.unpark(t1);
}
子线程休眠3s,主线程等了两秒决定唤醒。
1668489423790:st
1668489423790:t1:start
1668489425791:et
1668489425791:t1:end
修改代码顺序
t1.start();
u.unpark(t1);
System.out.println(System.currentTimeMillis() + ":st");
TimeUnit.SECONDS.sleep(2);
System.out.println(System.currentTimeMillis() + ":et");
1668489525548:st
1668489525548:t1:start
1668489525548:t1:end
1668489527548:et
线程调度总结
park有两个参数,第一个代表 isAbsolute,为true传入一个绝对时间,为false传入一个相对时间。第二个参数代表 Nanos纳秒。时间传入0代表一直阻塞。
unpark有一个参数传入要唤醒的线程。
如果先调用unpark,后面调用park不会产生阻塞的效果。
Volatile可见性
在 getAndSetObject 这几个类中用到了 getObjectVolatile 来保证获取到的值是最新的。
public class VolatileClient {
public static void main(String[] args) throws Exception {
Unsafe u = UnsafeUtil.get();
UnsafePojo pojo = new UnsafePojo();
pojo.setAddr("abc");
String addr = (String) u.getObjectVolatile(pojo,
u.objectFieldOffset(UnsafePojo.class.getDeclaredField("addr")));
System.out.println(addr);
System.out.println(pojo.getAddr());
}
}
内存控制 Memory
堆外内存
不受 jvm 管控的内存,这部分内存受操作系统控制。为什么使用? 因为在 io 方面具有优势。
DirectByteBuffer
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
DirectByteBuffer 中使用allocateMemory分配直接内存。
VM 参数 MaxDirectMemorySize
查找命令
java -XX:+PrintFlagsFinal -version | findstr Direct
-XX:MaxDirectMemorySize=10M
设置-XX:MaxDirectMemorySize=5M
long max = VM.maxDirectMemory();
System.out.println(max);
5242880
默认值
private static long directMemory = 67108864L;
经过计算为64M,所以默认字段为64M。
如果什么都不指定,输出 3506438144 / 1048576 = 3,344M, = 3.26G,应该跟系统内存有关。
如果仅指定 -Xmx10M,输出 9961472 / 1048576 = 9.5M。
allocateMemory
public native long allocateMemory(long var1);
c++源码
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
UnsafeWrapper("Unsafe_AllocateMemory");
size_t sz = (size_t)size;
if (sz != (julong)size || size < 0) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
if (sz == 0) {
return 0;
}
sz = round_to(sz, HeapWordSize);
void* x = os::malloc(sz, mtInternal);
if (x == NULL) {
THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
//Copy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
return addr_to_java(x);
UNSAFE_END
案例
根据c++源码分为了四种情况下面来测试对应输出是否正确。
第一种情况分配负数
u.allocateMemory(-1);
Exception in thread "main" java.lang.IllegalArgumentException
at sun.misc.Unsafe.allocateMemory(Native Method)
at com.example.common.juc.unsafe.MemoryClient.main(MemoryClient.java:16)
第二种情况分配0
long l = u.allocateMemory(0);
System.out.println(l);
0
第三种情况分配1MB空间
long _1MB = 1024 * 1024;
long l = u.allocateMemory(_1MB);
System.out.println(l);
550408256
第四种情况 if (x == NULL)
while (true) {
long l = u.allocateMemory(_1MB);
System.out.println(l);
}
24878571584
24879669312
24880758848
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at com.example.common.juc.unsafe.MemoryClient.main(MemoryClient.java:18)
reallocateMemory
public native long reallocateMemory(long var1, long var3);
c++源码
UNSAFE_ENTRY(jlong, Unsafe_ReallocateMemory(JNIEnv *env, jobject unsafe, jlong addr, jlong size))
UnsafeWrapper("Unsafe_ReallocateMemory");
void* p = addr_from_java(addr);
size_t sz = (size_t)size;
if (sz != (julong)size || size < 0) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
//如果大小为0释放内存
if (sz == 0) {
os::free(p);
return 0;
}
sz = round_to(sz, HeapWordSize);
void* x = (p == NULL) ? os::malloc(sz, mtInternal) : os::realloc(p, sz, mtInternal);
//重新分配失败返回oom
if (x == NULL) {
THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
return addr_to_java(x);
UNSAFE_END
根据源码可以看到第一个参数是地址,第二个参数是大小。主要作用是重新分配内存。