?阻礙Java獲得廣泛應(yīng)用的一個(gè)主要因素是Java程序的運(yùn)行效率。Java是介于解釋型和編譯型之間的一種語(yǔ)言,同樣的程序,如果用編譯型語(yǔ)言C來(lái)實(shí)現(xiàn),其運(yùn)行速度一般要比Java快一倍以上。Java具有平臺(tái)無(wú)關(guān)性,這使人們?cè)陂_(kāi)發(fā)企業(yè)級(jí)應(yīng)用的時(shí)候總是把它作為主要候選方案之一,但是性能方面的因素又大大削弱了它的競(jìng)爭(zhēng)力。為此,提高Java的性能就顯得十分重要。
? 問(wèn)題的提出
????? Sun公司及Java的支持者們?yōu)樘岣逬ava的運(yùn)行速度已經(jīng)做出了許多努力,其中大多數(shù)集中在程序設(shè)計(jì)的方法和模式選擇方面。由于算法和設(shè)計(jì)模式的優(yōu)化是通用的,對(duì)Java有效的優(yōu)化算法和設(shè)計(jì)模式,對(duì)其他編譯語(yǔ)言也基本同樣適用,因此不能從根本上改變Java程序與編譯型語(yǔ)言在執(zhí)行效率方面的差異。
JIT(Just In Time,及時(shí)編譯)技術(shù)是個(gè)比較好的思想。它的基本原理是:首先通過(guò)Java編譯器把Java源代碼編譯成平臺(tái)無(wú)關(guān)的二進(jìn)制字節(jié)碼。然后在Java程序真正執(zhí)行之前,系統(tǒng)通過(guò)JIT編譯器把Java的字節(jié)碼編譯為本地化機(jī)器碼。最后,系統(tǒng)執(zhí)行本地化機(jī)器碼,節(jié)省了對(duì)字節(jié)碼進(jìn)行解釋的時(shí)間。這樣做的優(yōu)點(diǎn)是大大提高了Java程序的性能,縮短了加載程序的時(shí)間;同時(shí),由于編譯的結(jié)果并不在程序運(yùn)行間保存,因此也節(jié)約了存儲(chǔ)空間。缺點(diǎn)是由于JIT編譯器對(duì)所有的代碼都想優(yōu)化,因此同樣也占用了很多時(shí)間。
動(dòng)態(tài)優(yōu)化技術(shù)是提高Java性能的另一個(gè)嘗試。該技術(shù)試圖通過(guò)把Java源程序直接編譯成機(jī)器碼,以充分利用Java動(dòng)態(tài)編譯和靜態(tài)編譯技術(shù)來(lái)提高Java的性能。該方法把輸入的Java源碼或字節(jié)碼轉(zhuǎn)換為經(jīng)過(guò)高度優(yōu)化的可執(zhí)行代碼和動(dòng)態(tài)庫(kù) (Windows中的. dll文件或Unix中的. so文件)。該技術(shù)能大大提高程序的性能,但卻破壞了Java的可移植性。
JNI技術(shù)
實(shí)際上,有一種通常為我們忽視的技術(shù)可以在很大程度上解決這個(gè)難題,那就是JNI(Java Native Interface, Java本地化方法)。主張采用純Java的人們通常反對(duì)本地化代碼的使用,他們認(rèn)為在Java程序執(zhí)行的過(guò)程中調(diào)用C/C++程序會(huì)影響程序的可移植性和安全性。還有一些人認(rèn)為JNI只是對(duì)過(guò)去混合編程技術(shù)的簡(jiǎn)單擴(kuò)展,其實(shí)際目的是為了充分利用大量原有的C程序庫(kù)。
其實(shí),我們不必拘泥于嚴(yán)格的平臺(tái)獨(dú)立性限制,因?yàn)椴捎肑NI技術(shù)只是針對(duì)一些嚴(yán)重影響Java性能的代碼段,該部分可能只占源程序的極少部分,所以幾乎可以不考慮該部分代碼在主流平臺(tái)之間移植的工作量。同時(shí),也不必過(guò)分擔(dān)心類型匹配問(wèn)題,我們完全可以控制代碼不出現(xiàn)這種錯(cuò)誤。此外,也不必?fù)?dān)心安全控制問(wèn)題,因?yàn)镴ava安全模型已擴(kuò)展為允許非系統(tǒng)類加載和調(diào)用本地方法。根據(jù)Java規(guī)范,從JDK 1. 2開(kāi)始,F(xiàn)indClass將設(shè)法找到與當(dāng)前的本地方法關(guān)聯(lián)的類加載器。如果平臺(tái)相關(guān)代碼屬于一個(gè)系統(tǒng)類,則無(wú)需涉及任何類加載器; 否則,將調(diào)用適當(dāng)?shù)念惣虞d器來(lái)加載和鏈接已命名的類。換句話說(shuō),如果在Java程序中直接調(diào)用C/C++語(yǔ)言產(chǎn)生的機(jī)器碼,該部分代碼的安全性就由Java虛擬機(jī)控制。
JNI實(shí)現(xiàn)步驟
編寫(xiě)JNI代碼的大致流程如下圖所示:
JNI實(shí)現(xiàn)流程圖
1. 首先編寫(xiě)需要JNI功能的Java類源文件。其中,需要JNI實(shí)現(xiàn)的方法應(yīng)當(dāng)用native關(guān)鍵字聲明。在該類中,用System. loadLibrary()方法加載需要的動(dòng)態(tài)鏈接庫(kù)。關(guān)鍵代碼如下:
//Compute.java
……
public class Compute {
public native double comp (double [] params);
……
static {
// 調(diào)用動(dòng)態(tài)鏈接庫(kù)
System. loadLibrary(“mathlib”);
}
……
}
2. 將該類源文件用Java類編譯器編譯成二進(jìn)制字節(jié)碼文件。由于采用了native關(guān)鍵字聲明,編譯器會(huì)忽視沒(méi)有代碼體的JNI方法部分。
3. 利用javah -jni *.class 生成相關(guān)JNI方法的頭文件。我們可以手工生成該文件,但是由于Java虛擬機(jī)是根據(jù)一定的命名規(guī)范完成對(duì)JNI方法的調(diào)用,所以手工編寫(xiě)頭文件需要特別小心。
上述文件產(chǎn)生的頭文件部分代碼如下:
//Compute. h
……
extern “C” {
JNIEXPORT jdouble JNICALL Java_Compute_comp (JNIEnv *, jobject, jdoubleArray);
}
……
可以看出,JNI函數(shù)名稱分為三部分:首先是Java關(guān)鍵字,供Java虛擬機(jī)識(shí)別;然后是調(diào)用者類名稱(全限定的類名,其中用下劃線代替名稱分隔符);最后是對(duì)應(yīng)的方法名稱,各段名稱之間用下劃線分割。
JNI函數(shù)的參數(shù)也由三部分組成: 首先是JNIEnv *,是一個(gè)指向JNI運(yùn)行環(huán)境的指針;第二個(gè)參數(shù)隨本地方法是靜態(tài)還是非靜態(tài)而有所不同——非靜態(tài)本地方法的第二個(gè)參數(shù)是對(duì)對(duì)象的引用,而靜態(tài)本地方法的第二個(gè)參數(shù)是對(duì)其 Java 類的引用; 其余的參數(shù)對(duì)應(yīng)通常 Java 方法的參數(shù),參數(shù)類型需要根據(jù)一定規(guī)則進(jìn)行映射。
4. 根據(jù)頭文件編寫(xiě)相應(yīng)方法的實(shí)現(xiàn)代碼。由于篇幅所限,具體的實(shí)現(xiàn)部分在此不再贅述。在編碼過(guò)程中,需要注意變量的長(zhǎng)度問(wèn)題,例如Java的整型變量長(zhǎng)度為32位,而C語(yǔ)言為16位,所以要仔細(xì)核對(duì)變量類型映射表,防止在傳值過(guò)程中出現(xiàn)問(wèn)題。
5. 利用C/C++編譯器將JNI實(shí)現(xiàn)代碼編譯成動(dòng)態(tài)鏈接庫(kù)。調(diào)用者類中需要顯式調(diào)用該鏈接庫(kù)。
在Win32環(huán)境下,可以利用Visual C ++或其他能產(chǎn)生DLL文件的C/C++編譯器將實(shí)現(xiàn)代碼編譯成動(dòng)態(tài)鏈接庫(kù)。筆者利用的是Microsoft.NET Framework的編譯器。編譯指令如下,其中%Java_HOME%是筆者的jdk安裝目錄變量:
cl -I%Java_HOME%\include
-I%Java_HOME%\include\win32
-LD jnicomp. c -Femathlib. dll
在Sun Soloaris下,相應(yīng)指令為:
cc -G -I/usr/local/java/include -I/usr/local/java/include/solaris jnicomp. c \
-o mathlib. so
注意,編譯的時(shí)候需要用I指令包含必要的庫(kù)文件路徑。
經(jīng)過(guò)上述處理,就基本上完成了一個(gè)包含本地化方法的Java類的開(kāi)發(fā)。
JNI技術(shù)的應(yīng)用
一些主要的Java技術(shù),如JDBC和RMI,大部分都采用JNI方式實(shí)現(xiàn)。但是,采用JNI確實(shí)會(huì)影響程序的平臺(tái)無(wú)關(guān)性,所以只能在特別需要的地方才能使用。通常來(lái)說(shuō),如果遇到下面的情況,我們可以考慮JNI:
● 需要直接操作物理設(shè)備,而沒(méi)有相關(guān)的驅(qū)動(dòng)程序,這時(shí)候我們可能需要用C甚至匯編語(yǔ)言來(lái)編寫(xiě)該設(shè)備的驅(qū)動(dòng),然后通過(guò)JNI調(diào)用;
● 涉及大量數(shù)學(xué)運(yùn)算的部分,用Java會(huì)帶來(lái)些效率上的損失;
● 用Java會(huì)產(chǎn)生系統(tǒng)難以支付的開(kāi)銷,如需要大量網(wǎng)絡(luò)鏈接的場(chǎng)合;
● 存在大量可重用的C/C++代碼,通過(guò)JNI可以減少開(kāi)發(fā)工作量,避免重復(fù)開(kāi)發(fā)。
另外,在利用JNI技術(shù)的時(shí)候要注意以下幾點(diǎn):
● 由于Java安全機(jī)制的限制,不要試圖通過(guò)Jar文件的方式發(fā)布包含本地化方法的Applet到客戶端;
● 注意內(nèi)存管理問(wèn)題,雖然在本地方法返回 Java 后將自動(dòng)釋放局部引用,但過(guò)多的局部引用將使虛擬機(jī)在執(zhí)行本地方法時(shí)耗盡內(nèi)存;
● JNI技術(shù)不僅可以讓Java程序調(diào)用C/C++代碼,也可以讓C/C++代碼調(diào)用Java代碼。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

