App下載

詳解Java為什么要調(diào)用本地代碼 以及調(diào)用elasticsearch的操作

猿友 2021-07-20 14:12:32 瀏覽數(shù) (2103)
反饋

一、為什么要進(jìn)行本地調(diào)用

1.基于性能的考慮

Java語(yǔ)言從其運(yùn)行速度上來(lái)說(shuō),在大多數(shù)方面是慢于底層操作系統(tǒng)上原生的C和C++等語(yǔ)言的。這主要是由于Java虛擬機(jī)這個(gè)中間層次的存在。如果完全用Java語(yǔ)言實(shí)現(xiàn)的性能無(wú)法達(dá)到程序的預(yù)期要求,可以選擇把部分重要且耗時(shí)的代碼用C或C++來(lái)實(shí)現(xiàn)。

2.基于某些特殊的需求

Java平臺(tái)提供的標(biāo)準(zhǔn)類庫(kù)的功能很強(qiáng)大,包括了在開(kāi)發(fā)中可能遇到的大部分功能。但是仍然有一些功能無(wú)法用標(biāo)準(zhǔn)API來(lái)實(shí)現(xiàn),主要是一些與底層硬件平臺(tái)直接交互的功能。Java虛擬機(jī)沒(méi)有把這一部分功能暴露給運(yùn)行在其上的程序。如果需要這方面的功能,那么只能使用原生代碼來(lái)進(jìn)行開(kāi)發(fā)。

3.與已有的使用原生代碼編寫(xiě)的程序之間進(jìn)行集成。

如果Java程序需要與底層操作系統(tǒng)上由C和C++語(yǔ)言開(kāi)發(fā)的程序進(jìn)行交互,那么可以進(jìn)行本地調(diào)用。

我們平時(shí)的開(kāi)發(fā)更多的情況是后邊兩種情況;在elasticsearch中基本上是屬于第二種情況。

二、使用JNI實(shí)現(xiàn)本地調(diào)用

針對(duì)以上提到的各種情況,Java提供了JNI(Java Native Interface)和JNA(Java Native Access)兩種方式,其中JNI的一個(gè)重要使用場(chǎng)景是提高程序的性能。當(dāng)對(duì)程序中關(guān)鍵部分的性能要求比較高的時(shí)候,可以使用C和C++代碼來(lái)實(shí)現(xiàn)。

我們先來(lái)看下怎么使用JNI來(lái)進(jìn)行本地調(diào)用。

首先我們需要有一個(gè)Java類來(lái)聲明本地方法,并負(fù)責(zé)加載本地代碼庫(kù)。本地方法與Java接口中的方法或抽象類中的抽象方法一樣,只包含方法聲明,沒(méi)有相關(guān)的實(shí)現(xiàn)。程序中的其他部分可以用正常的方法調(diào)用本地方法,比如參數(shù)傳遞和返回值使用等都與正常的方法相同。當(dāng)虛擬機(jī)在執(zhí)行本地方法時(shí),會(huì)嘗試在已經(jīng)加載的本地代碼庫(kù)中查找本地方法的對(duì)應(yīng)實(shí)現(xiàn)。在查找到對(duì)應(yīng)的實(shí)現(xiàn)方法之后,虛擬機(jī)會(huì)負(fù)責(zé)進(jìn)行參數(shù)傳遞、實(shí)際方法調(diào)用和返回值傳遞等工作。

public class HelloNative {
    static{
        System.loadLibrary("greetLib");
    }

    public static native  void greeting();
}

下一步要編寫(xiě)實(shí)現(xiàn)本地方法的C/C++代碼。Java提供的命令行工具根據(jù)Java源代碼生成C/C++代碼所需的頭文件。對(duì)于本地方法,頭文件中會(huì)包含相關(guān)的方法聲明與其對(duì)應(yīng)。

F:sourceJNIsrc>javac -h . .HelloNative.java

通過(guò)下邊自動(dòng)生成的頭文件,我們可以看到這里有很多的隱式約定,我們只要按照這個(gè)聲明進(jìn)行實(shí)現(xiàn)即可,具體的規(guī)則不是今天的重點(diǎn),不進(jìn)行詳述。

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

#ifndef _Included_HelloNative
#define _Included_HelloNative
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloNative
 * Method:    greeting
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloNative_greeting
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

三、elasticsearch使用JNA實(shí)現(xiàn)本地調(diào)用

通過(guò)上邊對(duì)JNI的簡(jiǎn)單了解,我們更多的時(shí)候碰到的情況是,在編寫(xiě)Java程序之前,就已經(jīng)有了可以使用的本地代碼庫(kù)。這個(gè)本地代碼庫(kù)可能是程序的一部分,也可能是底層操作系統(tǒng)自帶的。這些本地代碼庫(kù)的特點(diǎn)是在實(shí)現(xiàn)的時(shí)候并沒(méi)有考慮與Java虛擬機(jī)的集成,因此也沒(méi)有使用與JNI相關(guān)的內(nèi)容。在使用這樣的本地代碼庫(kù)時(shí),我們就需要一個(gè)中間的本地代碼庫(kù)作為橋梁。這個(gè)本地代碼庫(kù)作為Java程序中本地方法的實(shí)現(xiàn),負(fù)責(zé)實(shí)際調(diào)用時(shí)的參數(shù)類型轉(zhuǎn)換和返回值傳遞等工作。這個(gè)過(guò)程是十分的繁瑣的,Java提供了JNA來(lái)支持這種情況。

我們知道elasticsearch啟動(dòng)的時(shí)候需要檢測(cè)當(dāng)前用戶是否是root用戶,這個(gè)檢測(cè)是直接調(diào)用的底層操作系統(tǒng)的代碼,我們來(lái)看下elasticsearch是怎樣使用JNA實(shí)現(xiàn)的。

首先elasticsearch提供了Natives類,作為調(diào)用本地方法的入口,并負(fù)責(zé)檢測(cè)JNA的可用性。

 static {
        boolean v = false;
        try {
            // load one of the main JNA classes to see if the classes are available. this does not ensure that all native
            // libraries are available, only the ones necessary by JNA to function
            Class.forName("com.sun.jna.Native");
            v = true;
        } catch (ClassNotFoundException e) {
            logger.warn("JNA not found. native methods will be disabled.", e);
        } catch (UnsatisfiedLinkError e) {
            logger.warn("unable to load JNA native support library, native methods will be disabled.", e);
        }
        JNA_AVAILABLE = v;
    }

檢測(cè)JNA是否可用,然后再調(diào)用JNANatives的對(duì)用方法

static boolean definitelyRunningAsRoot() {
        if (!JNA_AVAILABLE) {
            logger.warn("cannot check if running as root because JNA is not available");
            return false;
        }
        return JNANatives.definitelyRunningAsRoot();
    }

在JNANatives的definitelyRunningAsRoot中,如果是非windows系統(tǒng),則調(diào)用
JNACLibrary.geteuid

/** Returns true if user is root, false if not, or if we don't know */
    static boolean definitelyRunningAsRoot() {
        if (Constants.WINDOWS) {
            return false; // don't know
        }
        try {
            return JNACLibrary.geteuid() == 0;
        } catch (UnsatisfiedLinkError e) {
            // this will have already been logged by Kernel32Library, no need to repeat it
            return false;
        }
    }

elasticsearch使用JNAKernel32Library來(lái)封裝對(duì)windows的Kernel32的調(diào)用,使用 JNACLibrary來(lái)封裝對(duì)非windows系統(tǒng)的libc的調(diào)用


四、總結(jié)

JNI更適合使用本地調(diào)用來(lái)解決對(duì)性能有更高要求的場(chǎng)景,需要我們自己使用C或者C++來(lái)實(shí)現(xiàn)處理邏輯。對(duì)于調(diào)用已有的本地庫(kù)的方法或者操作系統(tǒng)的方法,使用JNA更為方便便捷。

到此這篇關(guān)于 Java 調(diào)用 elasticsearch 本地代碼的文章就介紹到這了,想要了解更多相關(guān) Java 底層知識(shí)和Java應(yīng)用的其他內(nèi)容請(qǐng)搜索W3Cschool以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持!


0 人點(diǎn)贊