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 <jni.h>
/* 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_TestJNI_say方法,JNIEnv*表示的是JNI本身,通過它可以調用jni.h中定義好的函數,jobject表示當前對象(實例對象或者class)。我們不用太過關注,知道這裏定義了一個接口就行了。

有了頭文件,接下來我們要開始編寫實現、生成dll了,由於博主環境爲64位,所以需要生成64位環境的dll,使用的IDE是Visual Studio 2010。

首先,選擇文件-新建-項目,選擇模板Visual C++、Win32控制檯應用程序,項目名稱爲TestJNI.java中加載的庫名TestLib,同時我們要清楚項目位置,這裏使用的是默認配置:

然後點擊確定:

這裏點擊“下一步”,然後再應用程序設置中,選擇應用程序類型爲DLL,附加選項爲空項目,點擊完成:

這樣項目就創建好,接下來我們設置64位環境。首先進入配置管理器:

平臺在win32處下拉,選擇新建:

在新平臺處選擇64,從Win32處複製,勾選創建新的項目平臺:

然後點擊確定:

然後關閉配置管理器。

接下來我們創建頭文件,在項目上右鍵-添加-類,選擇C++類:

選擇之後,點擊添加。然後在嚮導中輸入類名,這裏我們使用前面定義的TestJNI就行了,點擊完成。

這裏就創建了TestJNI.h和TestJNI.cpp文件:

然後我們直接將前面使用javah命令生成的TestJNI.h文件內容拷貝到當前項目的TestJNI.h中,由於需要引用jni.h,所以我們需要將jni.h從%JAVA_HOME%\include目錄中拷貝一份。爲了簡單,這裏直接將jni.h放到TestJNI項目中,和TestJNI.h處於同一目錄,所以我們需要修改TestJNI.h中引用jni.h的方法,改爲相對路徑引入,即<>改爲"":

這裏我們打開jni.h簡單看一下,發現其中需要引入jni_md.h:

所以我們需要將jni.h文件和jni_md.h都拷貝到項目中,和TestJNI.h同目錄(jni_md.h在%JAVA_HOME%\include\win32目錄中):

接下來編寫TestJNI.cpp文件,在該文件中實現Java_TestJNI_say方法:簡單輸出hello world

#include "TestJNI.h"

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

到這裏我們代碼就編寫完畢了,接下來進入頂部導航欄:生成-重新生成解決方案

下方輸出區域會顯示執行結果:

這裏可能會被360等軟件攔截,允許即可。生成成功之後,我們進入項目目錄的x64\Debug目錄中,可以看到已經生成了dll文件了:

到這裏我們TestLib.dll動態鏈接庫已經創建好了,接下來將其複製到TestJNI.java目錄中,當然我們這裏是在桌面上:

然後我們打開命令窗口,先javac TestJNI.java編譯,然後java TestJNI運行代碼:

我們看到,輸出了hello world,native方法調用成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章