劉玉龍 朱文松
摘要:文章研究Java程序與本機代碼交互的機制,它通過在Java虛擬機(JVM)和本機代碼之間提供一組接口來實現(xiàn)。JNI可用于訪問本機庫、函數(shù)和數(shù)據(jù)結(jié)構(gòu)。通過具體實例的實現(xiàn),驗證了在保持Java平臺無關(guān)性的同時又充分發(fā)揮了本地平臺的優(yōu)勢。
關(guān)鍵詞:Java;JNI;Win32
doi:10.3969/J.ISSN.1672-7274.2024.04.026
中圖分類號:TP 309;TP 311.52? ? ? ? ? 文獻標志碼:B? ? ? ? ? ?文章編碼:1672-7274(2024)04-00-03
Research, Design, and Practice of Java Native Interface Technology
LIU Yulong1, ZHU Wensong2
(1. Graduate School of Anhui Jianzhu University, Hefei 230022, China;
2. Xianheng International Technology Co., Ltd., Hangzhou 310000, China)
Abstract: This article investigates the mechanism of interaction between Java programs and native code, which is achieved by providing a set of interfaces between the Java Virtual Machine (JVM) and native code. JNI can be used to access native libraries, functions, and data structures. Through the implementation of specific examples, it has been verified that the advantages of the local platform are fully utilized while maintaining Java platform independence.
Keywords: Java; JNI; Win32
在工程開發(fā)過程中,使用“純Java”代碼的解決方案是非常好的,但是有時候某些功能必須引入其他語言的支持。例如,在中小型企業(yè)級項目中經(jīng)常會有一個監(jiān)測系統(tǒng)性能的模塊,這個模塊需要使用和操作系統(tǒng)相關(guān)度較強的功能,如監(jiān)控磁盤剩余空間、內(nèi)存使用、處理器占用等。由于這些功能與平臺關(guān)聯(lián)性較強,Java沒有對這些功能提供支持。不過好在Java提供了JNI(Java Native Interface),它允許在Java虛擬機中運行的Java代碼與用其他編程語言(如C、C++和匯編)編寫的應(yīng)用程序和庫進行互操作。
1? ?JNI的開發(fā)步驟與動態(tài)鏈接庫
1.1 JNI的開發(fā)步驟
在Java項目中合適的位置加入本地方法,本地方法類似于抽象方法,只有方法簽名沒有方法實現(xiàn)。不同于抽象類和接口,擁有本地方法的類是一個完整的能夠?qū)嵗念?,只是它的方法由本地代碼實現(xiàn)。經(jīng)過編譯器編譯,帶有本地方法的類會生成一個字節(jié)碼文件。使用javah可以從這個字節(jié)碼文件產(chǎn)生一個C語言的頭文件,javah可以在jdk/bin目錄下找到。創(chuàng)建一個C/C++的工程用于創(chuàng)建動態(tài)鏈接庫,將上一步得到的頭文件加入到此工程中,根據(jù)頭文件編寫相應(yīng)方法的實現(xiàn)代碼。通常動態(tài)鏈接庫只在本地方法執(zhí)行時才需要加載,因此在本地方法所在的類中加入static代碼塊去加載動態(tài)鏈接庫即可[1]。
1.2 為什么要使用動態(tài)鏈接庫
在應(yīng)用程序開發(fā)過程中,一個用戶源程序變?yōu)橐粋€可在內(nèi)存中執(zhí)行的程序的步驟,需要經(jīng)歷編譯、鏈接、裝入三個主要過程。編譯是將源文件編譯為一個或多個目標模塊,鏈接是將標準代碼同使用的函數(shù)的目標代碼以及一些標準的啟動代碼組合起來生成程序的運行階段版本,裝入是將可執(zhí)行文件載入內(nèi)存中。
首先C/C++的產(chǎn)物是源文件編譯產(chǎn)生的目標文件,由于這些文件沒有經(jīng)過鏈接,不能使用任何外部的函數(shù)或功能,這類文件對于Java程序也就沒有任何作用。這些文件在經(jīng)過鏈接之后,會變成可執(zhí)行文件、靜態(tài)庫或動態(tài)庫。由于可執(zhí)行文件并不對外提供函數(shù)訪問入口,因此它也不是JNI使用的目標。靜態(tài)庫的使用需要將庫中的二進制碼和客戶端代碼合并到一起,但是字節(jié)碼和二進制碼有天然的差異性,難以合并到一起。經(jīng)過一系列排除,JNI只能通過調(diào)用動態(tài)鏈接庫中的函數(shù)完成本地調(diào)用[2]。
2? ?接口設(shè)計與JNI對象訪問
2.1 接口設(shè)計
在運維環(huán)境中,磁盤空間、CPU占用和內(nèi)存占用是系統(tǒng)運行中最重要的三個指標,本文就針對這三項指標進行接口設(shè)計。其中磁盤信息主要包括盤符、驅(qū)動器類型、總驅(qū)動器空間和可用空間。進程信息主要包括進程ID、內(nèi)存占用和CPU使用率。其中g(shù)etDiskInfo用于一次性獲取所有的驅(qū)動器信息,getProcessInfo根據(jù)進程ID獲取進程的運行信息。因為進程在每次啟動之后,通常會產(chǎn)生進程ID,所以通過進程ID獲取進程信息對外提供的功能依然有限。雖然進程ID經(jīng)常變化,但是進程名稱通常不會變化,因此設(shè)計getProcessId方法用于根據(jù)進程名稱獲取進程ID。
public class SystemInfoUtils {
public static native DriveInfo[] getDiskInfo(); // 獲取本地磁盤信息
public static native int getProcessId(String processName); // 根據(jù)名稱獲取進程ID
public static native ProcessInfo getProcessInfo(int processId); // 根據(jù)進程ID獲取進程信息
}
2.2 JNI對象訪問
雖然在C/C++代碼中,主要是按照C/C++的語言特性進行代碼開發(fā)的,因為需要實現(xiàn)Java的方法,那就避免不了C/C++變量和Java類型變量的轉(zhuǎn)換和操作。Java中的類型分為基本類型和對象類型兩大類,前者Java提供了對C/C++的直接類型映射,后者則需要通過一系列對象操作完成訪問。其中,Java對象訪問可以分為對象類型訪問、對象域訪問、靜態(tài)域訪問、靜態(tài)方法訪問和對象方法訪問。在使用javah生成的頭文件中,本地方法映射到的C/C++函數(shù)聲明的第一個參數(shù)都是JNIEnv類型的指針變量,這個數(shù)據(jù)結(jié)構(gòu)的元素指向JVM產(chǎn)生的矩陣的指針,矩陣中的每一個元素指向一個Java預(yù)定義的函數(shù)。使用這些函數(shù)就可以完成對對象的各種訪問。
3? ?DDL開發(fā)
3.1 磁盤信息讀取
磁盤信息的讀取主要就是WindowsAPI和JNI的使用過程,使用GetLogicalDriveStringsW函數(shù)可以獲取一個NULL結(jié)尾的字符串,這個字符串中每一個字符對應(yīng)系統(tǒng)中的一個有效驅(qū)動器。該函數(shù)的第二個參數(shù)需要先開辟一個字符數(shù)組作為緩沖區(qū)用于接收“NULL結(jié)尾的字符串”,為了防止數(shù)組訪問越界,還需要使用第一個參數(shù)告知該字符數(shù)組的長度。如果函數(shù)訪問成功,返回值就是復(fù)制到緩沖區(qū)的字符串的長度。這里獲取到的有效驅(qū)動器是指Windows支持的各種驅(qū)動,不僅包括磁盤驅(qū)動器,還包括光盤,軟盤等驅(qū)動器??梢酝ㄟ^GetDriveType函數(shù)獲取驅(qū)動器的類型,其中輸入?yún)?shù)是驅(qū)動器的根目錄,返回值是驅(qū)動器的類型。其中最常見的光盤驅(qū)動器DRIVE_CDROM和磁盤驅(qū)動器類型DRIVE_FIXED,更多類型可以參考Win32API的官方文檔[3]。
使用GetDiskFreeSpace函數(shù)可以檢索有關(guān)指定磁盤的信息,包括磁盤上的可用空間量。其中第一個輸入?yún)?shù)是驅(qū)動器的根目錄,其余四個分別用于接收簇上的扇區(qū)數(shù)、扇區(qū)中的字節(jié)數(shù)、磁盤上空閑的簇數(shù)和磁盤上總的簇數(shù)。根據(jù)這些指標就可以計算出這塊磁盤總的空間大小和可用空間大小。其中,塊(Block)/簇(Cluster)是磁盤管理中的邏輯概念,扇區(qū)是磁盤最小的物理存儲單元,但由于操作系統(tǒng)無法對數(shù)目眾多的扇區(qū)進行尋址,所以操作系統(tǒng)就將相鄰的扇區(qū)組合在一起,形成一個簇,然后再對簇進行管理。
3.2 進程管理
使用OpenProcess函數(shù)可以根據(jù)進程ID打開進程的句柄。句柄是Windows編程的一個基礎(chǔ),可以用來標志應(yīng)用程序中的不同對象和同類對象中的不同的實例。該函數(shù)的第一個參數(shù)是進程訪問權(quán)限,第二個參數(shù)是所得到的進程句柄是否可以被繼承,第三個參數(shù)是被打開進程的ID。在取得進程的句柄之后,可以通過GetProcessMemoryInfo檢索有關(guān)指定進程的內(nèi)存使用情況的信息。參數(shù)1是被訪問的目標進程的句柄,來自于之前OpenProcess函數(shù)的返回值。另外兩個參數(shù)分別是輸出結(jié)構(gòu)和輸出結(jié)構(gòu)變量的大小,輸出結(jié)構(gòu)接收了進程內(nèi)存使用情況的信息,其中當(dāng)前工作集大小WorkingSetSize就是目標進程占用的內(nèi)存大小,用字節(jié)表示。
CPU作為計算機中最重要的計算資源,其資源分配取決于操作系統(tǒng)使用的調(diào)度算法。對于不同的系統(tǒng)和系統(tǒng)目標,通常采用不同的調(diào)度算法,在Windows中主要采用基于時間片的輪轉(zhuǎn)式進程調(diào)度算法。這種調(diào)度算法,在早期采用的是簡單的時間片輪轉(zhuǎn)法,進入20世紀90年代后,開始采用多級反饋隊列調(diào)度算法。在Windows系統(tǒng)中,CPU的使用率類似于車輛的速度,是一段時間內(nèi)進程使用CPU時間在這段時間內(nèi)的占比。WindowsAPI沒有直接提供CPU使用率的獲取函數(shù),但是提供了GetProcessTimes函數(shù)用于獲取進程,從運行開始后,在內(nèi)核模式下執(zhí)行的時間量和在用戶模式下執(zhí)行的時間量[4]。
在Windows操作系統(tǒng)下有用戶模式和內(nèi)核模式兩種模式,根據(jù)處理器上運行的代碼的類型,處理器在兩個模式之間切換。應(yīng)用程序在用戶模式下運行,核心操作系統(tǒng)組件在內(nèi)核模式下運行。進程在兩種模式的執(zhí)行時間總量就是進程CPU的使用時間。通過在不同的時間點,兩次獲取進程CPU使用時間的差就是進程在這段時間內(nèi)使用的CPU時間,這個CPU時間在這段時間內(nèi)的占比就可以被認為是CPU的使用率。此外,多核處理器在現(xiàn)代計算機中基本上是標配,GetProcessTimes函數(shù)獲取的結(jié)果是進程在所有核心上的使用時間總量,所以最終結(jié)果還需要按核心數(shù)做平均值[5]。
最后根據(jù)名稱獲取進程的ID,使用EnumProcesses函數(shù)可以遍歷當(dāng)前系統(tǒng)中每個進程的進程標識符,然后再通過EnumProcessModules獲取進程的名稱,最后通過wcscmp比對程序名稱就可以獲取進程ID[6]。
int getCpuUsageRate(HANDLE hProcess) {
int cpu_num = get_processor_number();
FILETIME now;
GetSystemTimeAsFileTime(&now);
DWORD start = FileTimeToInt64(now);
DWORD used = (FileTimeToInt64(ker) + FileTimeToInt64(user));
Sleep(200);
GetSystemTimeAsFileTime(&now);
DWORD end = FileTimeToInt64(now);
DWORD used1 = (FileTimeToInt64(ker1) + FileTimeToInt64(user1));
return (used1 - used) * 100 / cpu_num / (end - start);
}
4? ?結(jié)束語
本文圍繞在Java中使用本地方法這一課題進行研究和思考,結(jié)合工程中常見的問題與相關(guān)理論進行技術(shù)設(shè)計和實現(xiàn)?,F(xiàn)在社會的發(fā)展方向就是信息化和智能化,隨著移動互聯(lián)網(wǎng)的興起,手機系統(tǒng)在人們生活中扮演著越來越重要的角色。作為現(xiàn)代社會最具時代特點的智能終端,想要充分發(fā)揮其硬件性能,不可避免地需要本地代碼驅(qū)動硬件,因此在安卓系統(tǒng)中JNI同樣扮演著重要角色。JAVA的應(yīng)用范圍雖然在不斷擴大,但是很多用戶僅僅從程序設(shè)計的語言方面了解JAVA。所以現(xiàn)在加強用戶對JAVA技術(shù)的認知是擴大JAVA的適用范圍的手段之一,也可為人們提供一個更加安全、更加簡便的計算機程序,為我國的計算機信息產(chǎn)業(yè)發(fā)展做出貢獻?!?/p>
參考文獻
[1] Cay S.Horstmann.Java核心技術(shù)[M].北京:機械工業(yè)出版社,2017.
[2] 湯小丹.計算機操作系統(tǒng)[M].西安:西安電子科技大學(xué)出版社,2007.
[3] Charles Petzold.Windows程序設(shè)計[M].北京:清華大學(xué)出版社,2010.
[4] 楊文超.Java虛擬機內(nèi)存管理與優(yōu)化策略[J].電子測試,2013(10):43-44,62.
[5]李卓恒.JAVA虛擬機相關(guān)技術(shù)研究與實踐[J].科技創(chuàng)新導(dǎo)報,2018(1):156,158.
[6] 許曉寧.Java_Native_Interface應(yīng)用研究[J].計算機科學(xué),2006(10):291-292.