簡述 眾所周知java
exe是java class文件的執行程序
但實際上java
exe程序只是
一個執行的外殼
它會裝載jvm
dll(windows下
以下皆以windows平台為例
linux下和solaris下其實類似
為
libjvm
so)
這個動態連接庫才是java
虛擬機的實際操作處理所在
本文探究java
exe程序是如何查找和裝載jvm
dll
動態庫
並調用它進行class文件執行處理的
源代碼 本文分析之代碼
《JavaTM
SDK
Standard Edition
v
fcs
Community Source Release》
可從sun官方網站下載
主要分析的源代碼為
j
se\src\share\bin\java
c
j
se\src\windows\bin\java_md
c
javac是什麼東西 java程序
源代碼
所謂
java程序
包括jdk中的java
exe\javac
exe\javadoc
exe
java
c源
代碼中通過JAVA_ARGS宏來控制生成的代碼
如果該宏沒定義則編譯文件控制生
成java
exe否則編譯文件控制生成其他的
java程序
比如
j
se\make\java\javac\Makefile(這是javac編譯文件)中
$(CD)
/
/sun/javac ; $(MAKE) $@ RELEASE=$(RELEASE) FULL_VERSION=$(FULL_VERSION)
j
se\make\sun\javac\javac\Makefile(由上面Makefile文件調用)中
JAVA_ARGS =
{ \
J
ms
m\
\
com
sun
tools
javac
Main\
}
則由同一份java
c代碼生成的javac
exe程序就會直接調用java類方法
com
sun
tools
javac
Main
這樣使其執行起來就像是直接運行的一個exe文件
而未定義JAVA_ARGS的java
exe程序則會調用傳遞過來參數中的類方法
從javac的main入口函數說起 main()函數中前面一段為重新分配參數指針的處理
然後調用函數
CreateExecutionEnvironment
該函數主要查找java運行環境的
目錄
和jvm
dll這個虛擬機核心動態連接庫文件路徑所在
根據操作系統不同
該函數有不同實現版本
但大體處理邏輯相同
我們看看windows平台該函數的處
理(j
se\src\windows\bin\java_md
c)
CreateExecutionEnvironment函數主要分為三步處理
a
查找jre路徑
b
裝載jvm
cfg中指定的虛擬機動態連接庫(jvm
dll)參數
c
取jvm
dll文件路徑
實現 a
查找jre路徑是通過java_md
c中函數
GetJREPath實現的
該函數首先調用GetApplicationHome函數
GetApplicationHome函數調用windows
API函數GetModuleFileName取java
exe程序的絕對路徑
以我的jdk安裝路徑為例
為
D:\java\j
sdk
_
\bin\java
exe
然後去掉文件名取絕對路徑為
D:\java\j
sdk
_
\bin
之後會在去掉最後一級目錄
現在絕對路徑為
D:\java\j
sdk
_
然後GetJREPath函數繼續判斷剛剛取的路徑+\bin\java
dll組合成的這個java
dll
文件是否存在
如果存在則
D:\java\j
sdk
_
為JRE路徑
否則判斷取得
的
D:\java\j
sdk
_
路徑+\jre\bin\java
dll文件是否存在
存在則
D:\java\j
sdk
_
\jre
為JRE路徑
如果上面兩種情況都不存在
則從注
冊表中去查找(參見函數GetPublicJREHome)
函數GetPublicJREHome先查找 HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\CurrentVersion
鍵值
當前JRE版本號
判斷
當前JRE版本號
是否為
做為版本號
如果是則
取HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\
當前JRE版本號
\JavaHome的路徑所在為JRE路徑
我的JDK返回的JRE路徑為
D:\java\j
sdk
_
\jre
b
裝載jvm
cfg虛擬機動態連接庫配置文件是通過java
c中函數:ReadKnownVMs實現
的
該函數首先組合jvm
cfg文件的絕對路徑
JRE路徑+\lib+\ARCH(CPU構架)+\jvm
cfg
ARCH(CPU構架)的判斷是通過java_md
c中GetArch函數判斷的
該函數中windows平
台只有兩種情況
WIN
的
ia
其他情況都為
i
我的為i
所以jvm
cfg
文件絕對路徑為
D:\java\j
sdk
_
\jre\lib\i
\jvm
cfg
文件內容如
下
## @(#)jvm
cfg
/
/
# # Copyright
Sun Microsystems
Inc
All rights reserved
# SUN PROPRIETARY/CONFIDENTIAL
Use is subject to license terms
# # ### List of JVMs that can be used as an option to java
javac
etc
# Order is important
first in this list is the default JVM
# NOTE that this both this file and its format are UNSUPPORTED and# WILL GO AWAY in a future release
## You may also select a JVM in an arbitrary location with the#
XXaltjvm=<jvm_dir>
option
but that too is unsupported# and may not be available in a future release
#
client KNOWN
server KNOWN
hotspot ALIASED_TO
client
classic WARN
native ERROR
green ERROR
(如果細心的話
我們會發現在JDK目錄中我的為
D:\java\j
sdk
_
\jre\bin\client
和
D:\java\j
sdk
_
\jre\bin\server
兩個目錄下都存在jvm
dll文件
而java正是通過jvm
cfg配置文件來管理這些不同版本的jvm
dll的
)
ReadKnownVMs函數會將該文件中的配置內容讀入到一個JVM配置結構的全局變量中
該函數首先跳過注釋(以
#
開始的行)
然後讀取以
開始的行指定的jvm參數
每一行為一個jvm信息
第一部分為jvm虛擬機名稱
第二部分為配置參數
比如行
client KNOWN
則
client
為虛擬機名稱
而
KNOWN
為配置類型參數
KNOWN
表示該虛擬機的jvm
dll存在
而
ALIASED_TO
表示為另一個jvm
dll的別名
WARN
表示該虛擬機的jvm
dll不存在但運行時會用其他存在的jvm
dll替代執行
而
ERROR
同樣表示該類虛擬機的jvm
dll不存在且運行時不會找存在的jvm
dll替代而直接拋出錯誤
信息
在運行java程序時指定使用那個虛擬機的判斷是由java
c中函數
CheckJvmType判斷
該函數會檢查java運行參數中是否有指定jvm的參數
然後從ReadKnownVMs函數讀取的jvm
cfg數據結構中去查找
從而指定不同的jvm類型(最終導致裝載不同jvm
dll)
有兩種方法可以指定jvm類型
一種按照jvm
cfg文件中的jvm名稱指定
第二種方法是直接指定
它們執行的方法分別是
java
J<jvm
cfg中jvm名稱>
java
XXaltjvm=<jvm類型名稱>
或
java
J
XXaltjvm=<jvm類型名稱>
如果是第一種參數傳遞方式
CheckJvmType函數會取參數
J
後面的jvm名稱
然後從已知的jvm配置參數中查找如果找到同名的則去掉該jvm名稱前的
直接返回該值
而第二種方法
會直接返回
XXaltjvm=
或
J
XXaltjvm=
後面的jvm類型名稱
如果在運行java時未指定上面兩種方法中的任一一種參數
CheckJvmType會取配置文件中第一個配置中的jvm名稱
去掉名稱前面的
返回該值
CheckJvmType函數的這個返回值會在下面的函數中匯同jre路徑組合成jvm
dll的絕對路徑
比如
如果在運行java程序時使用
java
J
client test
則ReadKnownVMs會讀取參數
client
然後查找jvm
cfg讀入的參數中是否有jvm名稱為
client
的
如果有則去掉jvm名稱前的
直接返回
client
而如果在運行java程序時使用如下參數
java
XXaltjvm=D:\java\j
sdk
_
\jre\bin\client test
則ReadKnownVMs
會直接返回
D:\java\j
sdk
_
\jre\bin\client
如果不帶上面參數執行如
java test
因為在jvm
cfg配置文件中第一個存在的jvm為
client
所以函數
ReadKnownVMs也會去掉jvm名稱前的
返回
client
其實這三中情況都是使用的
D:\java\j
sdk
_
\jre\bin\client\jvm
dll
這個jvm動態連接庫處理test這個class的
見下面GetJVMPath函數
c
取jvm
dll文件路徑是通過java_md
c中函數
GetJVMPath實現的
由上面兩步我們已經獲得了JRE路徑和jvm的類型字符串
GetJVMPath函數判斷CheckJvmType
返回的jvm類型字符串中是否包含了
\
或
/
如果包含則以該jvm類型字符串+\jvm
dll作為JVM的全路徑
否則以JRE路徑+\bin+\jvm類型字符串+\jvm
dll作為JVM的全路徑
看看上面的例子
第一種情況
java
J
client test
jvm
dll路徑為
JRE路徑+\bin+\jvm類型字符串+\jvm
dll 按照我的JDK路徑則為
D:\java\j
sdk
_
\jre
+
\bin
+
\client
+
\jvm
dll
第二種情況
java
XXaltjvm=D:\java\j
sdk
_
\jre\bin\client test
路徑為
jvm類型字符串+\jvm
dll即為
D:\java\j
sdk
_
\jre\bin\client
+
\jvm
dll
第三種情況
java test
為
D:\java\j
sdk
_
\jre
+
\bin
+
\client
+
\jvm
dll
與情況一相同
所以這三種情況都是調用的jvm動態連接庫
D:\javaj
sdk
_
\jre\bin\client\jvm
dll
處理test類的
我們來進一步驗證一下
打開cmd控制台
設置java裝載調試
E:\work\java_research>set _JAVA_LAUNCHER_DEBUG=
情況一
E:\work\java_research>java
J
client test
ScanDirectory
_JAVA_LAUNCHER_DEBUG
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26750.html