Java native 關鍵字

gary-liu發表於2016-08-11

今天在看Thread類,啟動執行緒用的 start()方法,該方法使執行緒開始執行,jvm 呼叫這個執行緒的run方法。(執行緒不能重複start),而 start()方法中又呼叫了 native start0()方法,該方法沒有實現體,其實現體是由非java語言在外面實現的,JVM將控制呼叫本地方法的所有細節。Object 類中也有很多 native 方法。

JNI

Java Native Interface (JNI)提供了若干的API實現了Java和其他語言的通訊(主要是 C&C++)。簡單地講,一個Native Method就是一個java呼叫非java程式碼的介面。有了本地方法,java程式可以做任何應用層次的任務。

使用Native Method 原因

有些層次的任務用java實現起來不容易,或者對程式的效率很在意時,如果要使用一些java語言本身沒有提供封裝的作業系統的特性時,那麼也需要使用本地方法。JVM支援著java語言本身和執行時庫,它是java程式賴以生存的平臺,它由一個直譯器(解釋位元組碼)和一些連線到原生程式碼的庫組成,甚至JVM的一些部分就是用C寫的。例如類 java.lang.Thread 的setPriority0()方法,這個本地方法是用 C 實現的,並被植入 JVM 內部,更多的情況是本地方法由外部的動態連結庫(external dynamic link library)提供,然後被JVM呼叫。

JVM如何呼叫 Native Method

當一個類第一次被使用到時,這個類的位元組碼會被載入到記憶體。在這個被載入的位元組碼的入口維持著一個該類所有方法描述符的 list,這些方法描述符包含這樣一些資訊:方法程式碼存於何處,它有哪些引數,方法的描述符(public之類)等等。
如果一個方法描述符內有 native,這個描述符塊將有一個指向該方法的實現的指標。這些實現在一些DLL檔案內,但是它們會被作業系統載入到java程式的地址空間。當一個帶有本地方法的類被載入時,其相關的DLL並未被載入,因此指向方法實現的指標並不會被設定。當本地方法被呼叫之前,這些DLL才會被載入,這是通過呼叫 java.system.loadLibrary()實現的。
使用 native 關鍵字說明這個方法是原生函式,也就是這個方法是用 C/C++ 語言實現的,並且被編譯成了DLL,由java去呼叫。

簡單例子

可以將native方法比作Java程式同C程式的介面,其實現步驟:

  1、在Java中宣告native()方法,然後編譯(javac);
  2、用 javah 命令產生一個.h檔案;
  3、寫一個 .c 檔案實現 native 匯出方法,其中需要包含第二步產生的.h檔案(注意其中又包含了 JDK 帶的 jni.h 檔案,該檔案在 $JAVA_HOME/include 目錄下);
  4、將第三步的 .c 檔案編譯成動態連結庫檔案;
  5、在 Java 中用 System.loadLibrary() 方法載入第四步產生的動態連結庫檔案,這個 native() 方法就可以在Java中被訪問了。

新建個 Java 檔案,內容如下。

public class HelloWorld{

       public native void sayHello();
       static{
          System.loadLibrary("HelloWorld");
       }

       public static void main(String[] args){
          new HelloWorld().sayHello();
      }

  }

按上面步驟執行命令

 javac HelloWorld.java      
 javah HelloWorld

HelloWorld.h 檔案內容


/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
 /* Header for class HelloWorld */

 #ifndef _Included_HelloWorld
 #define _Included_HelloWorld
 #ifdef __cplusplus
 extern "C" {
 #endif
 /*
 * Class:     HelloWorld
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_sayHello
  (JNIEnv *, jobject);

 #ifdef __cplusplus
}
#endif
#endif

寫個 .c 檔案實現 HelloWorld.h 檔案中的函式JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *, jobject); 檔案內容如下。

#include "HelloWorld.h"
#include <stdio.h>

JNIEXPORT void JNICALL Java_HelloWorld_sayHello(JNIEnv *env, jobject obj) 
{
    printf("Hello World!");
    return;
}

生產動態連結庫:

Windows——.DLL;Linux——.so;Mac OS X——.dylib; .dylib是Mach-O格式,也就是Mac OS X下的二進位制檔案格式。Mac OS X提供了一系列工具,用於建立和訪問動態連結庫。
編譯器/usr/bin/cc,也就是gcc了,Apple改過的
彙編器/usr/bin/as
連結器/usr/bin/ld // 可以合併.o檔案 ld -r -o c.o a.o b.o
Mac 有個自己的工具,/usr/bin/libtool,來建立動態連結庫,這個libtool不是
GNU的那個同名的libtool。

//生成sayHello.o
cc -I"/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/include/" -I"/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/include/darwin" -c SayHello.c

libtool -dynamic -o sayHello.dylib sayHello.o

生成 .o 檔案時,-I 是為了能夠載入到 jni.h 和 jni_md.h 檔案,
發現我的 libtool 已經是gun工具的了,貌似不能用,於是就還是使用了 gcc 來生成動態連結庫

gcc -fPIC -I"/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/include/" -I"/Library/Java/JavaVirtualMachines/jdk1.8.0_73.jdk/Contents/Home/include/darwin" -c HelloWorld.c 

//生成動態連結庫
gcc -fPIC -dynamiclib HelloWorld.o -o HelloWorld.dylib

再執行 java 檔案就可以了。

訪問動態連結庫
nm a.dylib可以看到匯出符號表等。
另一個Mac上的常用工具是 otool ,比如想看看c.dylib的依賴關係
otool -L a.dylib

[參考]
java Native Method初涉
JAVA中NATIVE關鍵字的作用

相關文章