JVM 本地方法棧(native method stack)解釋

本文內容如有錯誤、不足之處,歡迎技術愛好者們一同探討,在本文下面討論區留言,感謝。

什麼是本地方法棧?

oracle官方文檔jvms-se7

Java虛擬機的實現可以使用傳統的堆棧(俗稱“ C堆棧”)來支持native方法(用Java編程語言以外的語言編寫的方法)。解釋器的實現也可以使用諸如C之類的語言來解釋Java虛擬機的指令集,以使用native 本地方法棧。無法加載方法並且自身不依賴於常規堆棧的Java虛擬機實現無需提供本地方法棧。如果提供,通常在創建每個線程時爲每個線程分配本地方法棧。

該規範允許本地方法堆棧具有固定大小,或者根據計算要求動態擴展和收縮。如果本地方法棧的大小固定,則在創建每個本地方法棧的大小時可以獨立選擇。

Java虛擬機實現可以爲程序員或用戶提供對本地方法棧的初始大小的控制,並且在本地方法堆棧大小變化的情況下,可以控制最大和最小方法棧大小。

以下異常條件與本方法堆棧相關聯:

  1. 如果線程中的計算所需的本地方法棧超出允許的範圍,則Java虛擬機將拋出StackOverflowError。
  2. 如果可以動態擴展本法方法棧並嘗試進行本法方法棧擴展,但是可以提供足夠的內存,或者可以提供足夠的內存來爲新線程創建初始本法方法棧,則Java虛擬機將拋出OutOfMemoryError。

native方法是什麼?

參考資料:

  • https://stackoverflow.com/questions/6101311/what-is-the-native-keyword-in-java-for
  • https://stackoverflow.com/questions/18824798/what-is-difference-between-java-method-and-native-method
作用

native方法 也叫做本地方法。

  1. native 是Java中的關鍵字,表示平臺相關,用於調用本地代碼。
  2. native方法充當通過JNI(Java本地接口)或JNA(Java本地訪問)鏈接到其他編程語言(sun使用的是C/C++語言)之間的接口。比如:public native void method();

本地方法是以非Java語言開始的Java方法(Java方法是隻提供方法聲明,非Java語言提供具體實現)。本地方法可以訪問特定於系統的功能和API,而這些功能和API在Java中不直接可用(比如Unsafe類中的native方法,Unsafe只能通過反射進行創建)。

下面介紹stackoverflow上的一個最小實現來幫助理解上面的概念。

native 最小的實現

Main.java

public class Main {
    public native int square(int i);
    public static void main(String[] args) {
        System.loadLibrary("Main");
        System.out.println(new Main().square(2));
    }
}

Main.c

#include <jni.h>
#include "Main.h"

JNIEXPORT jint JNICALL Java_Main_square(
    JNIEnv *env, jclass obj, jint i) {
  return i * i;
}

編譯並運行:

sudo apt-get install build-essential openjdk-7-jdk
export JAVA_HOME='/usr/lib/jvm/java-7-openjdk-amd64'
javac Main.java
javah -jni Main
gcc -shared -fpic -o libMain.so -I${JAVA_HOME}/include \
  -I${JAVA_HOME}/include/linux Main.c
java -Djava.library.path=. Main

輸出:

4

上面是在Ubuntu 14.04 AMD64上測試。使用的是Oracle JDK 1.8.0_45

GitHub上的示例供您使用。

Java包/文件名中的下劃線必須使用_1進行轉義成C方法,如下所述:在包含下劃線的Android包名中調用JNI函數

解釋

動作:

  • 使用Java中的任意彙編代碼調用經過編譯的動態加載的庫(此處用C編寫)
  • 並將結果返回Java

作用:

  • 使用更好的CPU組裝指令(不是CPU可移植的)在關鍵部分上編寫更快的代碼
  • 進行直接系統調用

這樣做是以降低便攜性爲代價。當然也可以從C調用Java,但是必須首先在C中創建JVM:如何從C ++調用Java函數?

Android NDK

除了必須使用Android樣板進行設置外,此概念在此情況下完全相同。

官方的NDK存儲庫包含“規範”示例,例如hello-jni應用程序:

  • https://github.com/googlesamples/android-ndk/blob/4df5a2705e471a0818c6b2dbc26b8e315d89d307/hello-jni/app/src/main/java/com/example/hellojni/HelloJni.java#L39
  • https://github.com/googlesamples/android-ndk/blob/4df5a2705e471a0818c6b2dbc26b8e315d89d307/hello-jni/app/src/main/cpp/hello-jni.c#L27

此外,file/data/app/com.android.appname-*/oat/arm64/base.odex表示這是一個共享庫,我認爲這是與ART中的Java文件相對應的AOT預編譯.dex,另請參見:Android中的ODEX文件是什麼?那麼也許Java實際上也可以通過native接口運行?

OpenJDK 8中的示例

首先找到Object#clonejdk8u60-b27中定義的位置,並得出結論,它是通過native調用實現的。

發現

find . -name Object.java

jdk/src/share/classes/java/lang/Object.java#l212

protected native Object clone() throws CloneNotSupportedException;

現在來了困難的部分,在所有間接尋址中找到克隆的位置。使用查詢:find . -iname object.c
它將找到可能實現Object的本地方法的C或C ++文件。

jdk/share/native/java/lang/Object.c#l47

static JNINativeMethod methods[] = {
    ...
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

找下JVM_Clone符號:

grep -R JVM_Clone

進入hotspot/src/share/vm/prims/jvm.cpp#l580

JVM_ENTRY(jobject, JVM_Clone(JNIEnv* env, jobject handle))
    JVMWrapper("JVM_Clone");

擴展了一堆宏之後,得出的結論是這是定義點。

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