JRE 淺談

Java開發程序,發佈時總要考慮的問題就是怎麼在使用者的機器上裝好JRE。要考慮的問題很多:使用者有沒有能力獨自安裝JRE,使用者已有的 JRE 和我們需要的版本是不是一致,會不會出現版本問題,等等。使用.NET要考慮的問題就少些。現在.NET CLR似乎已經很普及了,看好多D版的 Win XP都會自己安裝最新的.NET CLR,而且似乎它的安裝界面也比JRE友好些。徹底解決安裝JRE的問題的方案,就是讓我們的應用程序自己揹着JRE!這樣,我們的程序就像傳統的Win32應用程序一樣,雙擊就可以執行,不用管所在的機器上是否有JRE,是什麼版本的JRE,無論怎樣,我有我自己的!要做到這一點,其實非常容易。

王森在他的《Java深度歷險》(強力推薦這本書,內容少而精)的第一章就解釋了JDK,JRE,JVM之間的關係。解釋了我們執行java.exe時發生的事情。其中提到,java.exe依照一套邏輯來尋找可以用的JRE,

1、首先查找自己所在的目錄下有沒有 JRE(據王森講這樣說不確切,我沒有JDK全部的源代碼,在此無從考證);(注:經過我的實際驗證,在windows2003+jdk 6的條件下,java.exe不會在自己的當前目錄下尋找JRE目錄)

2、其次查找自己的父目錄下有沒有JRE;(注:當時實驗時就這個問題鬱悶了半天,原因是開始我把JRE理解爲泛的java運行環境概念,而java.exe需要用的內容都在/JRE/LIB目錄下,所以我把這個目錄的內容放在父目錄下,但是不起作用。後來才知道,JRE是固定的目錄名稱而且必須使用這個名稱,並且必須將JRE安裝目錄下的內容都複製過來纔有效。也就是說,java.exe必須要在.../windows/底下找到JRE目錄,否則就按第3的邏輯找註冊表。sign :-(

3、最後纔是查詢Windows的註冊表(注:HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment)。

注1:思維發散---我這個人考慮問題總是這樣,容易渙散。java.exe的版本必須和他找到的JRE版本相匹配,否則會有錯誤。例如,安裝了JDK 6,在console下鍵入java -jar -verbose pw.zfb.jar, 自然就會用system32目錄下的java.exe執行, 並且在當前目錄和父目錄下沒有找到JRE,於是察看註冊表發現,CurrentVersion的值是1.4(該值可能被修改了),於是繼續在1.4的鍵值中找到JavaHome,從他的值中得到JRE的路徑,於是,1.6版的java使用了1.4版的JRE,so, error is occured. as follow:

C:\javatest>java -jar -verbose pw.zfb.jar >tt.txt
Registry key 'Software\JavaSoft\Java Runtime Environment\CurrentVersion'
has value '1.4', but '1.6' is required.
Error: could not find java.dll
Error: could not find Java 2 Runtime Environment.

同理,如果你發佈的java程序中自帶了JRE,請務必保證java.exe和JRE配套運行。這樣也避免了JRE不一致導致的問題。所以,開發Java程序或是執行Java 程序時,一定要記得兩件事:

只要這兩件事情確定了,就知道問題發生的來龍去脈,也可以很容易地解決很多貌似的怪異的問題。

注2:思維發散---上面查找規則中說的“首先查找自己所在的目錄下”中,自己所在的目錄指java.exe所在的目錄,比如,我們打開console,進入c:\根目錄下,在console中直接鍵入c:\java.exe,如果c:的根目錄下沒有java.exe, 那麼console怎麼找到java.exe呢,肯定是通過遍歷console的環境變量的path路徑去查找匹配。那麼我們當前的工作路徑是c:\,但是,java.exe尋找JRE的規則中,是查找自己所在的目錄和父目錄(也就是java.exe所在的路徑),而不是在當前工作路徑下尋找。而對於[JDK 5.0以上默認到當前工作目錄,以及JDK的lib目錄中尋找Java

L:\eclipse\workspace\JavaTest\arrays\IceCream.class 中使用了L:\eclipse\workspace\JavaTest\pw\zfb\marry.class中的類,如果我們在L:\eclipse\workspace\JavaTest\ 目錄下鍵入 java arrays.IceCream   那麼執行沒有問題,因爲按照[java.exe在JDK 5.0以上默認到當前工作目錄,以及JDK的lib目錄中尋找Java程序]這個規則他在當前工作目錄下找到了marry.class。但是如果我將IceCream.class 換個位置C:\javatest\arrays\IceCream.class ,那麼我在 c:\javatest下鍵入 java arrays.IceCream   就會說找不到marry.class. 這是設置classpath即可解決問題。C:\>set classpath=%classpath%;L:\eclipse\workspace\JavaTest。 如果要放在lib目錄下,就最好先打包成jar文件,然後再設置好classpath,也可以。(可參考:)

   通常我們在安裝好了JRE的機器上的任何一個目錄下都可以執行java.exe。因爲它在安裝時被複制到了windows的system32目錄下,而後者無論如何都會在path環境變量中。這個java.exe最終必然會訪問註冊表來確定真正的JRE的所在地(因爲,從windows xp開始,ms宣佈不再在OS中支持java,也就是不再在OS中提供JRE,所以,正常情況下,system32目錄下只有java.exe,而沒有JRE目錄及相關內容,上層windows目錄下也沒有JRE目錄及相關內容,所以,在他的當前目錄(system32)中,以及父目錄中(windows)無法找到JRE目錄,安裝上面總結的尋找邏輯java.exe只能去註冊表中尋找)。若我們要求每一個應用程序都自帶JRE,必然不能走這條路。但,邏輯的第二條講,java.exe會在它的父目錄下查找JRE,解決方案就在這一條中。
假設我們的應用程序打好了包,叫做 MyApp.jar,放在MyApp的目錄下。我們在MyApp目錄下,可以執行java ?jar MyApp.jar來運行我們的程序。我們安裝的是 JRE 1.5,在C:\Program Files\Java\jre1.5.0下。現在,我們只需要簡單的將jre1.5.0目錄搬到MyApp目錄下,順便改個容易寫的名字比如叫jre。現在,我們的應用程序的目錄結構就象這樣:


.../MyApp/
    --MyApp.jar
     --Jre


其中jre中是Jre1.5.0目錄下的全部內容。
Java.exe 就在jre目錄下的bin目錄中。根據第二條邏輯,java.exe會在它的父目錄中查找jre,實驗證實,它會查找lib目錄,而lib就在jre目錄下。因此,這樣java.exe就會確定jre的所在然後正常執行java程序,不會去管我們是否安裝了JRE,註冊表中是否有註冊項這些雜事了。
試一下,在命令行下進入MyApp的目錄下,假設它在C盤,將path指向MyApp下的JRE:
set path=c:\MyApp\jre\bin   (這時已經將原path中的設置清空了,不會再到windows\system32下尋找,如果不清空,那麼按照尋找次序,還是會先使用windows\system32下的java.exe。所以,一般情況下,自己的程序運行之前最好先在批處理文件裏面臨時設置PATH,把自己用的JRE放到PATH路徑最前面,所以肯定會運行自己帶的JRE,不會造成版本混亂。 )
然後運行:
java -verbose -jar MyApp.jar
加上verbose參數以確定我們確實用了這一套被搬出了家的JRE。
程序可以運行,並且在命令行輸出的前幾行,可以看到:
[Opened C:\MyApp\jre\lib\rt.jar]
[Opened C:\MyApp\jre\lib\jsse.jar]
[Opened C:\MyApp\jre\lib\jce.jar]
[Opened C:\MyApp\jre\lib\charsets.jar]
因此程序讀取的確實是它的私有的JRE。
至此,我們似乎完成了任務。但是現在我們的私有JRE仍不完美,缺點是太大。JRE 1.5有接近70MB,作爲我們的私有的JRE,好多內容都是可以拋棄的。Jre目錄下的license都可以不要,bin下的執行文件只需要保留java.exe或者javaw.exe,lib下只要保留rt,jsse, jce,charsets幾個庫就可以了。除了i386和zi兩個子目錄外,其餘的子目錄都可以不要。Zi下只需要保留自己地區的子目錄和其下的一些文件就可以。Lib下除了庫之外的屬性文件等等都要保留。這樣清理一番,JRE仍然有接近50MB。還可以繼續清理幾個庫文件裏面不需要的內容,這需要仔細的整理,會很費功夫。最好能寫出一個自動工具幫助我們整理它們。從Sun公司上下到的JMF裏面附帶的用Java寫的媒體播放器就自帶了JRE,只有幾個 MB。
清理過後需要運行幾遍我們的應用程序,以確保我們的JRE不缺少東西。
如果我們希望能有一個程序直接啓動我們的應用程序,那就還要費些功夫。最簡單的方法是弄出一個快捷方式來,但是快捷方式的路徑不能是相對的,不方便我們安裝。我想到的方案就是用Win32程序包裝一下。在VS.NET下寫一個Win32小程序:
int PASCAL WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow )
{
STARTUPINFO si;
PROCESS_INFORMATION pi;

ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( π, sizeof(pi) );

// Start the child process. 
if( !CreateProcess( "jre\\bin\\javaw.exe",//執行的程序名,該Win32小程序是和MyApp.jar放在一個目錄下的,所以,這裏使用相對路徑可以指定使用自己的JRE。同時注意,該win32程序必須在包裝的路徑(即MyApp\)中運行纔有效, 因爲他用的是相對路徑。有人問爲何不用註冊表?首先,這個程序的發佈是沒有安裝的過程的,那麼我怎麼知道用戶把它放在什麼路經下? 如果僅是針對windows平臺發佈,那麼MyApp.jar 是我們真正的java程序他保證了平臺無關性,而針對windows平臺的發佈,我們可以使用一個win32 wrapper,設置註冊表、環境變量等win32平臺特有的配置來解決anywhere runable的問題。            

"jre\\bin\\javaw.exe -jar MyApp.jar", // 帶參數的執行程序
NULL, // Process handle not inheritable. 
NULL, // Thread handle not inheritable. 
FALSE, // Set handle inheritance to FALSE. 
0, // No creation flags. 
NULL, // Use parent's environment block. 
NULL, // Use parent's starting directory. 
&si, // Pointer to STARTUPINFO structure.
π ) // Pointer to PROCESS_INFORMATION structure.

{
ErrorExit( "CreateProcess failed." );
}

// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );

// Close process and thread handles. 
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
}
基本上是按照MSDN文檔中的例子照搬的。將它編譯成一個EXE文件,我們的任務才全部完成。雙擊這個EXE文件,我們的程序啓動了,看起來和傳統的Win32程序沒有兩樣,JRE完全被隱藏在底層。

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