Duffblog

          前進一步,看看,需要前進更大一步才可以。

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            5 隨筆 :: 53 文章 :: 5 評論 :: 0 Trackbacks
          原文來自:http://www.matrix.org.cn

          摘要:

          眾所周知java.exe是java class文件的執行程序,但實際上java.exe程序只是一個執行的外殼,它會裝載jvm.dll(windows下,以下皆以windows平臺為例, linux下和solaris下其實類似,為:libjvm.so),這個動態連接庫才是java 虛擬機的實際操作處理所在。本文探究java.exe程序是如何查找和裝載jvm.dll 動態庫,并調用它進行class文件執行處理的.

          簡述
          眾所周知java.exe是java class文件的執行程序,但實際上java.exe程序只是
          一個執行的外殼,它會裝載jvm.dll(windows下,以下皆以windows平臺為例,
          linux下和solaris下其實類似,為:libjvm.so),這個動態連接庫才是java
          虛擬機的實際操作處理所在。本文探究java.exe程序是如何查找和裝載jvm.dll
          動態庫,并調用它進行class文件執行處理的。

          源代碼
          本文分析之代碼,《JavaTM 2 SDK, Standard Edition, v1.4.2 fcs
          Community Source Release》,可從sun官方網站下載,主要分析的源代碼為:
          j2se\src\share\bin\java.c
          j2se\src\windows\bin\java_md.c

          java.c是什么東西
          ‘java程序’源代碼
          所謂‘java程序’,包括jdk中的java.exe\javac.exe\javadoc.exe,java.c源
          代碼中通過JAVA_ARGS宏來控制生成的代碼,如果該宏沒定義則編譯文件控制生
          成java.exe否則編譯文件控制生成其他的‘java程序’。
          比如:
          j2se\make\java\javac\Makefile(這是javac編譯文件)中:
          $(CD) ../../sun/javac ; $(MAKE) $@ RELEASE=$(RELEASE) FULL_VERSION=$(FULL_VERSION)
          j2se\make\sun\javac\javac\Makefile(由上面Makefile文件調用)中:
          JAVA_ARGS = "{ \"-J-ms8m\", \"com.sun.tools.javac.Main\" }"
          則由同一份java.c代碼生成的javac.exe程序就會直接調用java類方法:
          com.sun.tools.javac.Main,這樣使其執行起來就像是直接運行的一個exe文件,
          而未定義JAVA_ARGS的java.exe程序則會調用傳遞過來參數中的類方法。

          從java.c的main入口函數說起
          main()函數中前面一段為重新分配參數指針的處理。
          然后調用函數:CreateExecutionEnvironment,該函數主要查找java運行環境的
          目錄,和jvm.dll這個虛擬機核心動態連接庫文件路徑所在。根據操作系統不同,
          該函數有不同實現版本,但大體處理邏輯相同,我們看看windows平臺該函數的處
          理(j2se\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\j2sdk1.4.2_04\bin\java.exe”,然后去掉文件名取絕對路徑為:
          “D:\java\j2sdk1.4.2_04\bin”,之后會在去掉最后一級目錄,現在絕對路徑為:
          “D:\java\j2sdk1.4.2_04”。
          然后GetJREPath函數繼續判斷剛剛取的路徑+\bin\java.dll組合成的這個java.dll
          文件是否存在,如果存在則“D:\java\j2sdk1.4.2_04”為JRE路徑,否則判斷取得
          的“D:\java\j2sdk1.4.2_04”路徑+\jre\bin\java.dll文件是否存在,存在則
          “D:\java\j2sdk1.4.2_04\jre”為JRE路徑。如果上面兩種情況都不存在,則從注
          冊表中去查找(參見函數GetPublicJREHome)。

          函數:GetPublicJREHome先查找
          HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\CurrentVersion
          鍵值“當前JRE版本號”,判斷“當前JRE版本號”是否為1.4做為版本號,如果是則
          取HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment\“當前JRE版本號”
          \JavaHome的路徑所在為JRE路徑。

          我的JDK返回的JRE路徑為:“D:\java\j2sdk1.4.2_04\jre”。

          b、裝載jvm.cfg虛擬機動態連接庫配置文件是通過java.c中函數:ReadKnownVMs實現
          的。
          該函數首先組合jvm.cfg文件的絕對路徑,JRE路徑+\lib+\ARCH(CPU構架)+\jvm.cfg
          ARCH(CPU構架)的判斷是通過java_md.c中GetArch函數判斷的,該函數中windows平
          臺只有兩種情況:WIN64的‘ia64’,其他情況都為‘i386’。我的為i386所以jvm.cfg
          文件絕對路徑為:“D:\java\j2sdk1.4.2_04\jre\lib\i386\jvm.cfg”。文件內容如
          下:
          #
          # @(#)jvm.cfg????????1.7 03/01/23
          #
          # Copyright 2003 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\j2sdk1.4.2_04\jre\bin\client”和“D:\java\j2sdk1.4.2_04\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\j2sdk1.4.2_04\jre\bin\client test”,則ReadKnownVMs
          會直接返回“D:\java\j2sdk1.4.2_04\jre\bin\client”;如果不帶上面參數執行如:
          “java test”,因為在jvm.cfg配置文件中第一個存在的jvm為“-client”,所以函數
          ReadKnownVMs也會去掉jvm名稱前的“-”返回“client”。其實這三中情況都是使用的
          “D:\java\j2sdk1.4.2_04\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\j2sdk1.4.2_04\jre”+“\bin”+“\client”+“\jvm.dll”。
          第二種情況“java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test”路徑為:
          jvm類型字符串+\jvm.dll即為:“D:\java\j2sdk1.4.2_04\jre\bin\client”+“\jvm.dll”
          第三種情況“java test”為:“D:\java\j2sdk1.4.2_04\jre”+“\bin”+“\client”
          +“\jvm.dll”與情況一相同。所以這三種情況都是調用的jvm動態連接庫“D:\java\
          j2sdk1.4.2_04\jre\bin\client\jvm.dll”處理test類的。

          我們來進一步驗證一下:
          打開cmd控制臺:

          設置java裝載調試
          E:\work\java_research>set _JAVA_LAUNCHER_DEBUG=1
          情況一
          E:\work\java_research>java -J-client test.ScanDirectory
          ----_JAVA_LAUNCHER_DEBUG----
          JRE path is D:\java\j2sdk1.4.2_04\jre
          jvm.cfg[0] = ->-client<-
          jvm.cfg[1] = ->-server<-
          jvm.cfg[2] = ->-hotspot<-
          jvm.cfg[3] = ->-classic<-
          jvm.cfg[4] = ->-native<-
          jvm.cfg[5] = ->-green<-
          299 micro seconds to parse jvm.cfg
          JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
          2897 micro seconds to LoadJavaVM
          JavaVM args:
          ????version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
          ????option[ 0] = '-Djava.class.path=.'
          ????option[ 1] = '-Dsun.java.command=test.ScanDirectory'
          50001 micro seconds to InitializeJVM
          Main-Class is 'test.ScanDirectory'
          Apps' argc is 0
          10208 micro seconds to load main class
          ----_JAVA_LAUNCHER_DEBUG----
          usage: java test.ScanDirectory DIR [output file]
          情況二
          E:\work\java_research>java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\client test.ScanDirectory
          ----_JAVA_LAUNCHER_DEBUG----
          JRE path is D:\java\j2sdk1.4.2_04\jre
          jvm.cfg[0] = ->-client<-
          jvm.cfg[1] = ->-server<-
          jvm.cfg[2] = ->-hotspot<-
          jvm.cfg[3] = ->-classic<-
          jvm.cfg[4] = ->-native<-
          jvm.cfg[5] = ->-green<-
          386 micro seconds to parse jvm.cfg
          JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
          2795 micro seconds to LoadJavaVM
          JavaVM args:
          ????version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
          ????option[ 0] = '-Djava.class.path=.'
          ????option[ 1] = '-Dsun.java.command=test.ScanDirectory'
          49978 micro seconds to InitializeJVM
          Main-Class is 'test.ScanDirectory'
          Apps' argc is 0
          9598 micro seconds to load main class
          ----_JAVA_LAUNCHER_DEBUG----
          usage: java test.ScanDirectory DIR [output file]
          情況三
          E:\work\java_research>java test.ScanDirectory
          ----_JAVA_LAUNCHER_DEBUG----
          JRE path is D:\java\j2sdk1.4.2_04\jre
          jvm.cfg[0] = ->-client<-
          jvm.cfg[1] = ->-server<-
          jvm.cfg[2] = ->-hotspot<-
          jvm.cfg[3] = ->-classic<-
          jvm.cfg[4] = ->-native<-
          jvm.cfg[5] = ->-green<-
          381 micro seconds to parse jvm.cfg
          JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll
          3038 micro seconds to LoadJavaVM
          JavaVM args:
          ????version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
          ????option[ 0] = '-Djava.class.path=.'
          ????option[ 1] = '-Dsun.java.command=test.ScanDirectory'
          50080 micro seconds to InitializeJVM
          Main-Class is 'test.ScanDirectory'
          Apps' argc is 0
          10215 micro seconds to load main class
          ----_JAVA_LAUNCHER_DEBUG----
          usage: java test.ScanDirectory DIR [output file]
          三個的JVM路徑都為:
          JVM path is D:\java\j2sdk1.4.2_04\jre\bin\client\jvm.dll

          其他情況
          E:\work\java_research>java -J-server test.ScanDirectory
          ----_JAVA_LAUNCHER_DEBUG----
          JRE path is D:\java\j2sdk1.4.2_04\jre
          jvm.cfg[0] = ->-client<-
          jvm.cfg[1] = ->-server<-
          jvm.cfg[2] = ->-hotspot<-
          jvm.cfg[3] = ->-classic<-
          jvm.cfg[4] = ->-native<-
          jvm.cfg[5] = ->-green<-
          377 micro seconds to parse jvm.cfg
          JVM path is D:\java\j2sdk1.4.2_04\jre\bin\server\jvm.dll
          2985 micro seconds to LoadJavaVM
          JavaVM args:
          ????version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
          ????option[ 0] = '-Djava.class.path=.'
          ????option[ 1] = '-Dsun.java.command=test.ScanDirectory'
          62382 micro seconds to InitializeJVM
          Main-Class is 'test.ScanDirectory'
          Apps' argc is 0
          12413 micro seconds to load main class
          ----_JAVA_LAUNCHER_DEBUG----
          usage: java test.ScanDirectory DIR [output file]
          E:\work\java_research>java -XXaltjvm=D:\java\j2sdk1.4.2_04\jre\bin\server test.ScanDirectory
          ----_JAVA_LAUNCHER_DEBUG----
          JRE path is D:\java\j2sdk1.4.2_04\jre
          jvm.cfg[0] = ->-client<-
          jvm.cfg[1] = ->-server<-
          jvm.cfg[2] = ->-hotspot<-
          jvm.cfg[3] = ->-classic<-
          jvm.cfg[4] = ->-native<-
          jvm.cfg[5] = ->-green<-
          376 micro seconds to parse jvm.cfg
          JVM path is D:\java\j2sdk1.4.2_04\jre\bin\server\jvm.dll
          2937 micro seconds to LoadJavaVM
          JavaVM args:
          ????version 0x00010002, ignoreUnrecognized is JNI_FALSE, nOptions is 2
          ????option[ 0] = '-Djava.class.path=.'
          ????option[ 1] = '-Dsun.java.command=test.ScanDirectory'
          62725 micro seconds to InitializeJVM
          Main-Class is 'test.ScanDirectory'
          Apps' argc is 0
          8942 micro seconds to load main class
          ----_JAVA_LAUNCHER_DEBUG----
          usage: java test.ScanDirectory DIR [output file]

          由上面可以看出,如果我們安裝了多個jdk或jre版本的話,使用“java -XXaltjvm=”
          可以通過絕對路徑指定到其他版本的jvm.dll上去,至于能不能運行還有待測試。

          我們下面回到java.c的main函數中看看上面找到的jvm.dll是如何裝載掛接執行的。

          該操作大致分為三步:
          a、裝載jvm.dll動態連接庫。
          b、初始化jvm.dll并掛接到JNIEnv(JNI調用接口)實例。
          c、調用JNIEnv實例裝載并處理class類。

          實現:
          a、裝載jvm.dll動態連接庫是由main函數調用java_md.c中LoadJavaVM函數實現的。
          main函數首先構造了一個InvocationFunctions結構的局部變量,InvocationFunctions
          結構有兩個函數指針:
          typedef struct {
          ????CreateJavaVM_t CreateJavaVM;
          ????GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
          } InvocationFunctions;

          函數LoadJavaVM中先調用windows API函數:LoadLibrary裝載jvm.dll動態連接庫,
          之后將jvm.dll中的導出函數JNI_CreateJavaVM和JNI_GetDefaultJavaVMInitArgs
          掛接到InvocationFunctions變量的CreateJavaVM和GetDefaultJavaVMInitArgs函數
          指針變量上。jvm.dll的裝載工作宣告完成。

          b、初始化jvm.dll并掛接到JNIEnv(JNI調用接口)實例是通過java.c中函數:
          InitializeJVM完成的。
          main方法中首先定義了一個JNIEnv結構的指針,JNIEnv結構中定義了許多與裝載class
          類文件、查找類方法、調用類方法有關的函數指針變量。InitializeJVM會調用上面
          以掛接jvm.dll中JNI_CreateJavaVM的InvocationFunctions結構變量的CreateJavaVM方法,即調用jvm.dll中函數JNI_CreateJavaVM,該函數會將JNIEnv結構的實例返回到main中的JNIEnv結構的指針上。這樣main中的JNIEnv指針獲取了JNIEnv實例后,就可以開始對class文件進行處理了。

          c、調用JNIEnv實例裝載并處理class類。
          a)如果是執行jar包。
          如果執行的是一個jar包的話,main函數會調用java.c中的函數:GetMainClassName,該函數使用JNIEnv實例構造并調用java類:java.util.jar.JarFile中方法getManifest()并從返回的Manifest對象中取getAttributes("Main-Class")的值,即jar包中文件:
          META-INF/MANIFEST.MF指定的Main-Class的主類名作為運行的主類。
          之后main函數會調用java.c中LoadClass方法裝載該主類(使用JNIEnv實例的FindClass)。
          b)如果是執行class方法。
          main函數直接調用java.c中LoadClass方法裝載該類。

          然后main函數調用JNIEnv實例的GetStaticMethodID方法查找裝載的class主類中
          “public static void main(String[] args)”方法,并判斷該方法是否為public方法,然后調用JNIEnv實例的CallStaticVoidMethod方法調用該java類的main方法。

          總結
          由上面的代碼分析可以看出幾個問題。
          a、為什么JDK和JRE不一定通過安裝,直接拷到硬盤上,設置path環境變量就可以執行。因為java運行獲取jre路徑的首選方法正是直接通過獲取java.exe絕對路徑來判斷的,如果通過修改注冊表選項而不設置path環境變量也可以找到jre路徑所在。修改方法如下:
          首先我們將java.exe拷到任意目錄下,我的拷到e:\temp下,在cmd中運行:
          清空path環境變量
          E:\temp>set path=
          E:\temp>java
          Error opening registry key 'Software\JavaSoft\Java Runtime Environment'
          Error: could not find java.dll
          Error: could not find Java 2 Runtime Environment.

          導入如下注冊表文件(java.reg)
          Windows Registry Editor Version 5.00

          [HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft]

          [HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment]
          "CurrentVersion"="1.4"

          [HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.4]
          "JavaHome"="D:\\java\\j2sdk1.4.2_04\\jre"


          再執行顯示執行正常,如下:
          E:\temp>java
          Usage: java [-options] class [args...]
          ?????????? (to execute a class)
          ?? or??java [-options] -jar jarfile [args...]
          ?????????? (to execute a jar file)

          where options include:
          ????-client?????? to select the "client" VM
          ????-server?????? to select the "server" VM
          ????-hotspot??????is a synonym for the "client" VM??[deprecated]
          ??????????????????The default VM is client.

          ????-cp <class search path of directories and zip/jar files>
          ????-classpath <class search path of directories and zip/jar files>
          ??????????????????A ; separated list of directories, JAR archives,
          ??????????????????and ZIP archives to search for class files.
          ????-D<name>=<value>
          ??????????????????set a system property
          ????-verbose[:class|gc|jni]
          ??????????????????enable verbose output
          ????-version??????print product version and exit
          ????-showversion??print product version and continue
          ????-? -help??????print this help message
          ????-X????????????print help on non-standard options
          ????-ea[:<packagename>...|:<classname>]
          ????-enableassertions[:<packagename>...|:<classname>]
          ??????????????????enable assertions
          ????-da[:<packagename>...|:<classname>]
          ????-disableassertions[:<packagename>...|:<classname>]
          ??????????????????disable assertions
          ????-esa | -enablesystemassertions
          ??????????????????enable system assertions
          ????-dsa | -disablesystemassertions
          ??????????????????disable system assertions

          b、java.exe是通過jvm.cfg文件或直接指定jvm.dll路徑來裝載執行java程序的。
          見上面例子。
          c、不同實現版本的jvm.dll必然存在一個名為:JNI_CreateJavaVM的導出函數,
          java.exe正是通過調用該函數獲得JNIEnv調用接口來裝載執行class類的。這個
          函數也是我們下一步研究java vm實作技巧的研究出發點。
          JNI_CreateJavaVM函數位于:hotspot\src\share\vm\prims\jni.cpp文件中。

          原文連接地址:http://www.matrix.org.cn/resource/article/1/1650_jvm_loading_progress.html



          posted on 2006-04-04 20:32 追球者 閱讀(339) 評論(0)  編輯  收藏 所屬分類: Java
          主站蜘蛛池模板: 万年县| 丽江市| 莱州市| 洱源县| 应城市| 新巴尔虎右旗| 西青区| 修文县| 呼图壁县| 保定市| 凤城市| 黄浦区| 湄潭县| 玛沁县| 文山县| 淮阳县| 镇赉县| 汉中市| 苍梧县| 霍州市| 泊头市| 汉寿县| 翼城县| 台山市| 鄯善县| 齐齐哈尔市| 崇礼县| 西充县| 吴桥县| 襄樊市| 兴义市| 临夏县| 辽宁省| 普陀区| 武夷山市| 达州市| 饶平县| 平顶山市| 吉首市| 柳江县| 丰城市|