深入OpenJDK源码核心探秘Unsafe(含JNI完整使用流程)

本文介绍了Java中的sun.misc.Unsafe类,它提供了一种直接操作内存的方式,常用于高性能场景。尽管直接使用Unsafe存在风险,但通过反射仍能获取其实例。文章详细探讨了Unsafe的CAS操作,如compareAndSwapInt和compareAndSwapLong的源码实现,并简要提及了JNI的使用流程,展示了如何通过JNI调用本地方法。
摘要由CSDN通过智能技术生成

一、介绍

在Java中,sun.misc.Unsafe可以认为是用于JDK内部使用的工具类,它将一些需要使用native语言实现的功能通过java方法暴露出来,这些方法比较“危险”,因为它们可以直接修改内存中的值。

通常情况下,我们并不能直接在程序中使用Unsafe,Unsafe的构造方法被私有化,语法层面上只能通过其提供的公共静态方法getUnsafe获取Unsafe实例:theUnsafe,theUnsafe在静态代码块被初始化,所以其是一个单例,如下所示:

public final class Unsafe {
    //静态的、私有的实例字段
    private static final Unsafe theUnsafe;
......
    //私有化构造器
    private Unsafe() {
        }

......
    //对外提供静态方法获取实例
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        //检查是否允许访问Unsafe
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
......
    static {
        ......    
        //初始化Unsafe实例
        theUnsafe = new Unsafe();
        ......
    }
}

从Unsafe的代码中可以看出,通过getUnsafe方法获取Unsafe单例是唯一的“正规”途径,而该方法中对调用者classLoader做了检查,我们看一下检查代码:VM.isSystemDomainLoader:

public static boolean isSystemDomainLoader(ClassLoader var0) {
    return var0 == null;
}

逻辑比较简单,主要是看classLoader是否为null,也就是判断该类是不是由BootStrap加载,如果不是的话,则不具有访问Unsafe的权限,随即抛出SecurityException异常。

import sun.misc.Unsafe;

public class Test{

    public static void main(String[] args){
        Unsafe unsafe = Unsafe.getUnsafe();
    }
}

上述代码尝试获取一个Unsafe实例,会抛出异常异常:

Exception in thread "main" java.lang.SecurityException: Unsafe
	at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)

二、使用Unsafe

虽然我们没有权限调用Unsafe#getUnsafe方法,但是我们还是有办法能够获取到Unsafe实例。既然theUnsafe字段在静态代码块中被初始化了,并且它是一个静态变量,那么我们可以直接通过反射获取该字段值。

在下面的代码中,我们先获取到了Unsafe实例,并且通过Unsafe#putInt方法将Test实例的value字段设置为100,然后通过Unsafe#getIntVolatile方法获取该值,并输出:

public class Test {
    private int value;

    public static void main(String[] args) throws Exception {
        //获取theUnsafe字段
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        //私有变量设置访问权限
        unsafeField.setAccessible(true);
        //theUnsafe是静态变量,直接通过Field#get(null)获取
        Unsafe theUnsafe = (Unsafe) unsafeField.get(null);

        //创建Test实例
        Test test = new Test();
        //获取value字段在Test中的偏移量
        long fieldOffset = theUnsafe.objectFieldOffset(Test.class.getDeclaredField("value"));
        //直接操作该内存,设置值
        theUnsafe.putInt(test, fieldOffset, 100);
        //获取该偏移的值
        System.out.println(theUnsafe.getIntVolatile(test, fieldOffset));
    }
}

控制台输出为:100

三、源码实现

Unsafe中的所有基础方法都属于native方法,这里我们就不一一解析了,就看一下JUC中经常使用的CAS源码实现即可,至于其它方法,读者朋友可以根据自身情况酌情研究。

在openjdk的源码中,Unsafe主要的实现代码在unsafe.cpp中,其中包含了native方法操作内存的实现细节。这里我们主要看看以下两个native方法的实现:

    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);

2.1 compareAndSwapInt

我们首先看看compareAndSwapInt方法在openjdk中的实现:

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

首先是JNIHandles::resolve(obj)方法。该方法定义在jniHandles.hpp中:

inline oop JNIHandles::resolve(jobject handle) {
  oop result = (handle == NULL ? (oop)NULL : *(oop*)handle);
  assert(result != NULL || (handle == NULL || !CheckJNICalls || is_weak_global_handle(handle)), "Invalid value read from jni handle");
  assert(result != badJNIHandle, "Pointing to zapped jni handle area");
  return result;
};

可以看到,该方法定义为一个内联函数,没有做什么复杂的事儿,就是把jobject参数转成了oop。

然后调用index_oop_from_field_offset_long(p, offset);方法,其中offset为修改的字段在对象所占内存中的偏移位置(相关信息可参考Java对象内存布局),最终得到的addr就是该字段在内存中的位置,这里可以简单理解为对象地址加上offset。

得到字段地址addr之后就会调用核心的Atomic::cmpxchg方法,该方法定义在atomic.hpp中,除了cmpxchg,还有xchg、cmpxchg_ptr等等,我们简单看一下:

class Atomic : AllStatic {
 public:
  // Atomic operations on jlong types are not available on all 32-b
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值