深入浅出java并发编程(Unsafe)

参考链接

JVM源码分析之堆外内存完全解读

Unsafe工具类的一些实用技巧,通往JVM底层的钥匙

基于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可见性

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

根据源码可以看到第一个参数是地址,第二个参数是大小。主要作用是重新分配内存。

putOrderedLong

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值