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

一、介绍

在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-bit
  // platforms. If atomic ops on jlongs are defined here they must only
  // be used from code that verifies they are available at runtime and
  // can provide an alternative action if not - see supports_cx8() for
  // a means to test availability.

  // Atomically store to a location
  inline static void store    (jbyte    store_value, jbyte*    dest);
  inline static void store    (jshort   store_value, jshort*   dest);
  inline static void store    (jint     store_value, jint*     dest);
  // See comment above about using jlong atomics on 32-bit platforms
  inline static void store    (jlong    store_value, jlong*    dest);
  inline static void store_ptr(intptr_t store_value, intptr_t* dest);
  inline static void store_ptr(void*    store_value, void*     dest);

  inline static void store    (jbyte    store_value, volatile jbyte*    dest);
  inline static void store    (jshort   store_value, volatile jshort*   dest);
  inline static void store    (jint     store_value, volatile jint*     dest);
  // See comment above about using jlong atomics on 32-bit platforms
  inline static void store    (jlong    store_value, volatile jlong*    dest);
  inline static void store_ptr(intptr_t store_value, volatile intptr_t* dest);
  inline static void store_ptr(void*    store_value, volatile void*     dest);

  // See comment above about using jlong atomics on 32-bit platforms
  inline static jlong load(volatile jlong* src);

  // Atomically add to a location, return updated value
  inline static jint     add    (jint     add_value, volatile jint*     dest);
  inline static intptr_t add_ptr(intptr_t add_value, volatile intptr_t* dest);
  inline static void*    add_ptr(intptr_t add_value, volatile void*     dest);
  // See comment above about using jlong atomics on 32-bit platforms
         static jlong    add    (jlong    add_value, volatile jlong*    dest);

  // Atomically increment location
  inline static void inc    (volatile jint*     dest);
         static void inc    (volatile jshort*   dest);
  inline static void inc_ptr(volatile intptr_t* dest);
  inline static void inc_ptr(volatile void*     dest);

  // Atomically decrement a location
  inline static void dec    (volatile jint*     dest);
         static void dec    (volatile jshort*    dest);
  inline static void dec_ptr(volatile intptr_t* dest);
  inline static void dec_ptr(volatile void*     dest);

  // Performs atomic exchange of *dest with exchange_value.  Returns old prior value of *dest.
  inline static jint         xchg(jint         exchange_value, volatile jint*         dest);
         static unsigned int xchg(unsigned int exchange_value, volatile unsigned int* dest);

  inline static intptr_t xchg_ptr(intptr_t exchange_value, volatile intptr_t* dest);
  inline static void*    xchg_ptr(void*    exchange_value, volatile void*   dest);

  // Performs atomic compare of *dest and compare_value, and exchanges *dest with exchange_value
  // if the comparison succeeded.  Returns prior value of *dest.  Guarantees a two-way memory
  // barrier across the cmpxchg.  I.e., it's really a 'fence_cmpxchg_acquire'.
         static jbyte    cmpxchg    (jbyte    exchange_value, volatile jbyte*    dest, jbyte    compare_value);
  inline static jint     cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value);
  // See comment above about using jlong atomics on 32-bit platforms
  inline static jlong    cmpxchg    (jlong    exchange_value, volatile jlong*    dest, jlong    compare_value);

         static unsigned int cmpxchg(unsigned int exchange_value,
                                     volatile unsigned int* dest,
                                     unsigned int compare_value);

  inline static intptr_t cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value);
  inline static void*    cmpxchg_ptr(void*    exchange_value, volatile void*     dest, void*    compare_value);
};

cmpxchg实现在atomic.cpp中,最终会根据具体的宿主环境内联具体的实现,具体我们参照include的atomic.inline.hpp头文件:

// Linux
#ifdef TARGET_OS_ARCH_linux_x86
# include "atomic_linux_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_sparc
# include "atomic_linux_sparc.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_zero
# include "atomic_linux_zero.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_arm
# include "atomic_linux_arm.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_linux_ppc
# include "atomic_linux_ppc.inline.hpp"
#endif

// Solaris
#ifdef TARGET_OS_ARCH_solaris_x86
# include "atomic_solaris_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_solaris_sparc
# include "atomic_solaris_sparc.inline.hpp"
#endif

// Windows
#ifdef TARGET_OS_ARCH_windows_x86
# include "atomic_windows_x86.inline.hpp"
#endif

// BSD
#ifdef TARGET_OS_ARCH_bsd_x86
# include "atomic_bsd_x86.inline.hpp"
#endif
#ifdef TARGET_OS_ARCH_bsd_zero
# include "atomic_bsd_zero.inline.hpp"
#endif

#endif // SHARE_VM_RUNTIME_ATOMIC_INLINE_HPP

以x86的linux环境为例,其引入的是atomic_linux_x86.inline.hpp头文件,其中实现的内联函数较多,我们先只看看jint的cmpxchg函数实现:

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

第一步调用了os::is_MP()函数,该函数定义在os.hpp中,主要是判断当前环境是否为多处理器环境:

  // Interface for detecting multiprocessor system
  static inline bool is_MP() {
    assert(_processor_count > 0, "invalid processor count");
    return _processor_count > 1 || AssumeMP;
  }

使用__asm__ volatile嵌入汇编代码片段,一个内联汇编表达式分为四个部分,以:进行分隔。使用__asm__来声明表达式,volatile则保证指令不会被gcc优化影响,关于gcc内联汇编的更多内容不是本文的主题(博主也忘差不多了o(╯□╰)o),感兴趣的朋友可以自行了解。cmpxchgl指令则是x86的比较并交换指令,如果是多处理器会使用lock前缀,关于lock前缀,我们在总结volatile的博文中介绍过,其可以达到一个内存屏障的效果,也可以参照intel手册。

从上面也看到了,Atomic::cmpxchg的方法的返回值是内存中的

2.2 compareAndSwapLong

同样先在unsafe.cpp中找到compareAndSwapLong的实现:

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

和compareAndSwapInt不一样的是,jlong类型的CAS操作需要判断宿主平台是否支持8字节的CAS操作,也就是通过VM_Version::supports_cx8方法进行判断。该方法定义在vm_version.hpp中:

  // does HW support an 8-byte compare-exchange operation?
  static bool supports_cx8()  {
#ifdef SUPPORTS_NATIVE_CX8
    return true;
#else
    return _supports_cx8;
#endif
  }

其中_supports_cx8默认为false,在相应的平台下会有对应的方法进行赋值,比如在x86下,会在vm_version_x86.cpp中调用supports_cmpxchg8()方法进行赋值,该方法定义在vm_version_x86.hpp中:

  static bool supports_cmpxchg8() { return (_cpuFeatures & CPU_CX8) != 0; }

这个_cpuFeatures可以理解为cpu的一些属性,其在vm_version_x86.cpp中处理:如果支持CPUID,则会通过调用feature_flags()方法获取:

if (cpu_family() > 4) { // it supports CPUID
      _cpuFeatures = feature_flags();
      // Logical processors are only available on P4s and above,
      // and only if hyperthreading is available.
      _logical_processors_per_package = logical_processor_count();
    }

我们来看一下feature_flags方法:

  static uint32_t feature_flags() {
    uint32_t result = 0;
    if (_cpuid_info.std_cpuid1_edx.bits.cmpxchg8 != 0)
      result |= CPU_CX8;
    if (_cpuid_info.std_cpuid1_edx.bits.cmov != 0)
      result |= CPU_CMOV;
    if (_cpuid_info.std_cpuid1_edx.bits.fxsr != 0 || (is_amd() &&
        _cpuid_info.ext_cpuid1_edx.bits.fxsr != 0))
      result |= CPU_FXSR;
    // HT flag is set for multi-core processors also.
    if (threads_per_core() > 1)
      result |= CPU_HT;
    if (_cpuid_info.std_cpuid1_edx.bits.mmx != 0 || (is_amd() &&
        _cpuid_info.ext_cpuid1_edx.bits.mmx != 0))
      result |= CPU_MMX;
    if (_cpuid_info.std_cpuid1_edx.bits.sse != 0)
      result |= CPU_SSE;
    if (_cpuid_info.std_cpuid1_edx.bits.sse2 != 0)
      result |= CPU_SSE2;
    if (_cpuid_info.std_cpuid1_ecx.bits.sse3 != 0)
      result |= CPU_SSE3;
    if (_cpuid_info.std_cpuid1_ecx.bits.ssse3 != 0)
      result |= CPU_SSSE3;
    if (_cpuid_info.std_cpuid1_ecx.bits.sse4_1 != 0)
      result |= CPU_SSE4_1;
    if (_cpuid_info.std_cpuid1_ecx.bits.sse4_2 != 0)
      result |= CPU_SSE4_2;
    if (_cpuid_info.std_cpuid1_ecx.bits.popcnt != 0)
      result |= CPU_POPCNT;
    if (_cpuid_info.std_cpuid1_ecx.bits.avx != 0 &&
        _cpuid_info.std_cpuid1_ecx.bits.osxsave != 0 &&
        _cpuid_info.xem_xcr0_eax.bits.sse != 0 &&
        _cpuid_info.xem_xcr0_eax.bits.ymm != 0) {
      result |= CPU_AVX;
      if (_cpuid_info.sef_cpuid7_ebx.bits.avx2 != 0)
        result |= CPU_AVX2;
    }
    if (_cpuid_info.std_cpuid1_edx.bits.tsc != 0)
      result |= CPU_TSC;
    if (_cpuid_info.ext_cpuid7_edx.bits.tsc_invariance != 0)
      result |= CPU_TSCINV;
    if (_cpuid_info.std_cpuid1_ecx.bits.aes != 0)
      result |= CPU_AES;
    if (_cpuid_info.sef_cpuid7_ebx.bits.erms != 0)
      result |= CPU_ERMS;
    if (_cpuid_info.std_cpuid1_ecx.bits.clmul != 0)
      result |= CPU_CLMUL;

    // AMD features.
    if (is_amd()) {
      if ((_cpuid_info.ext_cpuid1_edx.bits.tdnow != 0) ||
          (_cpuid_info.ext_cpuid1_ecx.bits.prefetchw != 0))
        result |= CPU_3DNOW_PREFETCH;
      if (_cpuid_info.ext_cpuid1_ecx.bits.lzcnt != 0)
        result |= CPU_LZCNT;
      if (_cpuid_info.ext_cpuid1_ecx.bits.sse4a != 0)
        result |= CPU_SSE4A;
    }

    return result;
  }

从方法体中可以看到,如果从CPUID中判断到支持cmpxchg8,那么会将result和CPU_CX8进行按位或,CPU_CX8定义在枚举中,表示哪一位是cmpxchg8是否支持的标识:

  enum {
    CPU_CX8    = (1 << 0), // next bits are from cpuid 1 (EDX)
    CPU_CMOV   = (1 << 1),
    CPU_FXSR   = (1 << 2),
    CPU_HT     = (1 << 3),
    CPU_MMX    = (1 << 4),
    CPU_3DNOW_PREFETCH  = (1 << 5), // Processor supports 3dnow prefetch and prefetchw instructions
                                    // may not necessarily support other 3dnow instructions
    CPU_SSE    = (1 << 6),
    CPU_SSE2   = (1 << 7),
    CPU_SSE3   = (1 << 8), // SSE3 comes from cpuid 1 (ECX)
    CPU_SSSE3  = (1 << 9),
    CPU_SSE4A  = (1 << 10),
    CPU_SSE4_1 = (1 << 11),
    CPU_SSE4_2 = (1 << 12),
    CPU_POPCNT = (1 << 13),
    CPU_LZCNT  = (1 << 14),
    CPU_TSC    = (1 << 15),
    CPU_TSCINV = (1 << 16),
    CPU_AVX    = (1 << 17),
    CPU_AVX2   = (1 << 18),
    CPU_AES    = (1 << 19),
    CPU_ERMS   = (1 << 20), // enhanced 'rep movsb/stosb' instructions
    CPU_CLMUL  = (1 << 21) // carryless multiply for CRC
  } cpuFeatureFlags;

这里可以看到,用_cpuFeatures的最低位标识cmpxchg8,如果支持,那么将该位通过 | 操作设置为1,判断的时候像这样简单判断即可:

_cpuFeatures & CPU_CX8 != 0

我们回到Unsafe_CompareAndSwapLong处,如果支持cmpxchg8的话,那么和jint类似,会调用Atomic::cmpxchg方法实现CAS操作,一下是linux_x86下的内联实现:

inline jlong    Atomic::cmpxchg    (jlong    exchange_value, volatile jlong*    dest, jlong    compare_value) {
  bool mp = os::is_MP();
  __asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
                        : "=a" (exchange_value)
                        : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                        : "cc", "memory");
  return exchange_value;
}

注意前面jint的CAS使用的是cmpxchgl指令,而此处使用的是cmpxchgq指令。当然,如果不支持cmpxchg8的话,就只能自己处理,而且需要保证原子性,所以使用了ObjectLocker,上锁之后实现比较并交换逻辑即可:

jboolean success = false;
    ObjectLocker ol(p, THREAD);
    if (*addr == e) { *addr = x; success = true; }
    return success;

最后,CAS操作返回的是布尔类型的结果,使用ObjectLocker时,从代码上就很好理解,先比较地址上的值,如果相等则交换,返回true,否则返回false。而使用Atomic::cmpxchg时,结果是cmpxchg返回值和期望值比较的结果:

(Atomic::cmpxchg(x, addr, e)) == e

回到上面cmpxchg的内联实现,其返回的是exchange_value。关于cmpxchgl和cmpxchgq指令,如果期望值和当前值相等的话,那么会将新值写到内存地址,覆盖当前值,并且返回被覆盖的值(旧值,其实也是期望值);如果期望值和当前值不相等,那么会返回最新的当前值。所以这个操作叫做比较并交换,不论比较的结果如何,都会“换出”内存地址的当前值。

所以,只有当返回的exchange_value和我们的期望值相等时,才表示指令执行成功了,否则表示该内存上的值被其它线程更新了,指令失败。

四、JNI实现

现在我们再回过头来看JNI(Java Native Interface),即Java本地接口,它能将Java与c/c++等本地代码进行集成,使得Java代码能够与其它编程语言进行相互操作。JNI是一个接口,类似于虚拟机规范,其并没有限制实现细节。

某一些功能我们可能无法用Java语言直接实现(或者说高效实现),那么就需要借助其他本地语言实现,我们只需要在Java中定义好接口(native),JVM在调用方法时会根据指定的方法名从本地库中寻找具体的实现,这个实现可能由c++编写。

我们这里实现一个JNI调用的流程。

首先,我们编写Java文件,TestJNI.java:

class TestJNI{
	public native void say();
	static{
		System.loadLibrary("TestLib");
	}
	public static void main(String[] args){
		new TestJNI().say();
	}
}

在TestJNI.java中,我们定义了一个native方法say(),该方法我们会以c++实现。在static静态代码块中加载动态库,该库中需要包含say方法的本地实现,所以接下来我们需要组建TestLib动态库(TestLib.dll)。dll即Dynamic Link Library,可以想象为我们Java中的jar包,里面包含一系列可供他人调用的公用函数。

注:这里为了简单,我直接把TestJNI.java放到桌面上操作。

我们的native方法是定义在TestJNI.java中的,现在要用c++实现该方法,需要定义该方法的头文件,并且JNI对native方法的方法名有规范,并不能随便写,因为JVM寻找本地实现的时候有自己的规则。这样看来编写头文件还比较麻烦,好在jdk为我们提供了指令:javah,以快速创建该头文件。

C:\Users\Administrator\Desktop>javah TestJNI

C:\Users\Administrator\Desktop>

调用javah TestJNI之后,会生成一个TestJNI.h文件,我们来看一下文件内容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class TestJNI */

#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     TestJNI
 * Method:    say
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_TestJNI_say
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

我们看到TestJNI.h中引入了jni.h,该文件位于%JAVA_HOME%\include目录中:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第1张图片

文件中定义了一个Java_TestJNI_say方法,JNIEnv*表示的是JNI本身,通过它可以调用jni.h中定义好的函数,jobject表示当前对象(实例对象或者class)。我们不用太过关注,知道这里定义了一个接口就行了。

有了头文件,接下来我们要开始编写实现、生成dll了,由于博主环境为64位,所以需要生成64位环境的dll,使用的IDE是Visual Studio 2010。

首先,选择文件-新建-项目,选择模板Visual C++、Win32控制台应用程序,项目名称为TestJNI.java中加载的库名TestLib,同时我们要清楚项目位置,这里使用的是默认配置:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第2张图片

然后点击确定:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第3张图片

这里点击“下一步”,然后再应用程序设置中,选择应用程序类型为DLL,附加选项为空项目,点击完成:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第4张图片

这样项目就创建好,接下来我们设置64位环境。首先进入配置管理器:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第5张图片

平台在win32处下拉,选择新建:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第6张图片

在新平台处选择64,从Win32处复制,勾选创建新的项目平台:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第7张图片

然后点击确定:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第8张图片

然后关闭配置管理器。

接下来我们创建头文件,在项目上右键-添加-类,选择C++类:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第9张图片

选择之后,点击添加。然后在向导中输入类名,这里我们使用前面定义的TestJNI就行了,点击完成。

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第10张图片

这里就创建了TestJNI.h和TestJNI.cpp文件:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第11张图片

然后我们直接将前面使用javah命令生成的TestJNI.h文件内容拷贝到当前项目的TestJNI.h中,由于需要引用jni.h,所以我们需要将jni.h从%JAVA_HOME%\include目录中拷贝一份。为了简单,这里直接将jni.h放到TestJNI项目中,和TestJNI.h处于同一目录,所以我们需要修改TestJNI.h中引用jni.h的方法,改为相对路径引入,即<>改为"":

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第12张图片

这里我们打开jni.h简单看一下,发现其中需要引入jni_md.h:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第13张图片

所以我们需要将jni.h文件和jni_md.h都拷贝到项目中,和TestJNI.h同目录(jni_md.h在%JAVA_HOME%\include\win32目录中):

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第14张图片

接下来编写TestJNI.cpp文件,在该文件中实现Java_TestJNI_say方法:简单输出hello world

#include "TestJNI.h"

JNIEXPORT void JNICALL Java_TestJNI_say (JNIEnv *env , jobject obj){
	printf("hello world");
}

到这里我们代码就编写完毕了,接下来进入顶部导航栏:生成-重新生成解决方案

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第15张图片

下方输出区域会显示执行结果:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第16张图片

这里可能会被360等软件拦截,允许即可。生成成功之后,我们进入项目目录的x64\Debug目录中,可以看到已经生成了dll文件了:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第17张图片

到这里我们TestLib.dll动态链接库已经创建好了,接下来将其复制到TestJNI.java目录中,当然我们这里是在桌面上:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第18张图片

然后我们打开命令窗口,先javac TestJNI.java编译,然后java TestJNI运行代码:

Java深入JVM源码核心探秘Unsafe(含JNI完整使用流程)_第19张图片

我们看到,输出了hello world,native方法调用成功。

你可能感兴趣的:(jvm,Unsafe,对象内存布局,JNI,CAS,JAVA,JVM,JVM源码分析)