執行Java,執行java檔案
最近在閱讀JVM源碼,把一些心得寫成Blog分享出來,於是便新開了這麼一個專題。
第一篇文章取名字的時候讓我非常困惑,我代碼的閱讀是從Launcher開始入手的,也就是Java.exe(如果是windows平台的話)對應的相關代碼,但我又不能取“JVM啟動過程分析”之類的名字,因為從分析主流程的角度來講還深不到這個層次,所以就暫且起了這麼一個奇怪的名字。
這個系列如果能繼續下去的話,不加特殊說明,使用的JDK和JVM版本均為8u20,來自OpenJDK:http://hg.openjdk.java.net/jdk8u
本人是一個JAVA程式員,在分析JVM大量C/C++時難免會有不妥當的地方,還希望各位讀者指正。介於時間有涯而代碼“無涯”,具體的實現細節不可能面面俱到,只著重分析和看懂大致流程和機制,如果有比較重要的細節遺漏之處,歡迎留言討論。
在這個系列中,任何對源檔案位置的描述都使用相對路徑,其中jdk/代表放置Jdk源碼的根目錄,hotspot/代表放置jvm源碼的根目錄。
一、Launcher程式碼分析
(1)Main.c中的main
位置:jdk/src/bin/main.c
當我們調用java命令時,首先肯定進入的是C/C++的main函數,就像若干年前我寫的那個helloworld一樣。
這個main函數位於jdk/src/bin/main.c,在JDK8中是放置在這個位置,在以前較老版本是放在JVM相關代碼中的。
main.c中幾乎沒有實質的邏輯,主要是複製一些參數,處理在windows平台中的一些調用,之後就把各參數傳遞給JLI_launch進行執行。相關代碼在第125行。
return JLI_Launch(margc, margv, sizeof(const_jargs) / sizeof(char *), const_jargs, sizeof(const_appclasspath) / sizeof(char *), const_appclasspath, FULL_VERSION, DOT_VERSION, (const_progname != NULL) ? const_progname : *margv, (const_launcher != NULL) ? const_launcher : *margv, (const_jargs != NULL) ? JNI_TRUE : JNI_FALSE, const_cpwildcard, const_javaw, const_ergo_class);
(2)java.c中的JLI_Launch
位置:jdk/src/bin/java.c
java.c中根據注釋可以大概看出這個函數的各參數含義:
JLI_Launch(int argc, char ** argv, /* main argc, argc */ int jargc, const char** jargv, /* java args */ int appclassc, const char** appclassv, /* app classpath */ const char* fullversion, /* full version defined */ const char* dotversion, /* dot version defined */ const char* pname, /* program name */ const char* lname, /* launcher name */ jboolean javaargs, /* JAVA_ARGS */ jboolean cpwildcard, /* classpath wildcard*/ jboolean javaw, /* windows-only javaw */ jint ergo /* ergonomics class policy */
在第236行調用系統函數擷取環境變數,給jrepath,jvmpath和jvmcfg賦值。(每個Java基礎教程裡設定的環境變數在這裡起作用)
CreateExecutionEnvironment(&argc, &argv, jrepath, sizeof(jrepath), jvmpath, sizeof(jvmpath), jvmcfg, sizeof(jvmcfg));
第248行載入jvm.dll這個檔案,只是負載檔案到記憶體,並沒有執行任何操作,同時給這個ifn結構體賦值(根據dll抽取函數調用地址賦值),ifn結構體包含三個關鍵的函數指標。
if (!LoadJavaVM(jvmpath, &ifn)) { return(6); }Ifn結構如下:
typedef struct { CreateJavaVM_t CreateJavaVM; GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs; GetCreatedJavaVMs_t GetCreatedJavaVMs;} InvocationFunctions;根據函數名可以大概猜到,第一個是建立虛擬機器,第二個是擷取初始參數,第三個是建立很多虛擬機器?(vms代表虛擬機器的複數形式?我猜的。。)。
之後又對參數進行了一些額外的處理(包括擷取classpath、列印調試資訊、添加額外的參數等等),在299行調用JVMInit初始化虛擬機器
return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
(3)Java.c中的JVMInit
位置:jdk/src/bin/java.c
代碼很少,首先列印一下資訊,然後調用ConitueInNewThread函數,從函數名也可以看出,會啟用一個新線程建立JVM。
intJVMInit(InvocationFunctions* ifn, jlong threadStackSize, int argc, char **argv, int mode, char *what, int ret){ ShowSplashScreen(); return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);}
(4)Java.c中的ContinueInNewThread
位置:jdk/src/bin/java.c
這個函數依然沒做啥東西,就是封裝了一下參數,然後委派給ContinueInNewThread0。而ContinueInNewThread0開啟一個新的線程,執行JavaMain函數。
(5)Java.c中的JavaMain
位置:jdk/src/bin/java.c
首先在第371行初始化JVM,而InitializeJVM函數做的工作就是調用ifn->createJavaVM,具體的JVM啟動過程水太深因此不在這裡進行分析。如果初始化成功,則會給vm對象和env對象賦值,其中env是一個非常重要的對象,進行JNI調用的時候會頻繁用到。
if (!InitializeJVM(&vm, &env, &ifn)) { JLI_ReportErrorMessage(JVM_ERROR1); exit(1); }之後檢查一下是否傳入了Jar檔案或者一個類名,如果沒有的話就列印一下Usage
之後第439行擷取主類,擷取主類的方式挺有意思的,在後面會進行分析。接著看看是否拋異常,如果拋異常就直接退出了。
mainClass = LoadMainClass(env, mode, what); CHECK_EXCEPTION_NULL_LEAVE(mainClass);
隨後針對JavaFX載入一下東西(如果需要的話),然後擷取main這個方法的ID,組合參數,並Invoke,水到渠成。
其中每一步都檢查是否有異常拋出。
mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V"); CHECK_EXCEPTION_NULL_LEAVE(mainID); /* Build platform specific argument array */ mainArgs = CreateApplicationArgs(env, argv, argc); CHECK_EXCEPTION_NULL_LEAVE(mainArgs); /* Invoke main method. */ (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); /* * The launcher's exit code (in the absence of calls to * System.exit) will be non-zero if main threw an exception. */ ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
(未完待續)
java檔案是怎執行的
首先:使用javac命令把.java檔案編譯成.class檔案(位元組碼檔案)
然後:JVM(java虛擬機器)裝載.class檔案並翻譯成機器碼後運行java程式;
共同學習-----請參考:
我們手工執行java程式是這樣的:
1 在記事本中或者是UE的文字編輯器中,寫好來源程式;
2 使用javac命令把來源程式編譯成.class檔案;
編譯後的.class(類位元組碼)檔案中會包含以下內容:
ConstantPool:符號表;
FieldInfo:類中的成員變數資訊;
MethodInfo:類中的方法描述;
Attribute:可選的附加節點。
FieldInfo節點包含成員變數的名稱,諸如public,private,static等的標誌。ConstantValue屬性用來儲存靜態不變的成員變數的值。Deprecated和Synthetic被用來標記一個成員變數是不被推薦的或由編譯器產生的。
3 有了.class檔案,我們執行 java 解釋命令就可以運行java程式了。
現在我們主要討論一下,當執行 java這個命令後,會發生什麼事情呢?
首先,JVM裝載.class,也就是類裝載器裝載類位元組碼。一個類裝載器本身也是一個java類,所以,類裝載器自身也需要被另外一個類裝載器裝載,這就出現了類似先有蛋,還是先有雞的問題。但JAVA中的類裝載器的這個問題卻很容易解決。JAVA的虛擬機器(JVM)中內嵌了一個稱為Bootstrap類裝載器,它是用特定於作業系統的本地代碼實現的,屬於JAVA虛擬機器的核心,Bootstrap類不用專門的類裝載器去進行裝載。Bootstrap類負責載入JAVA核心包中的類(即rt.jar檔案中的類),這些類的Class.getClassLoader()方法傳回值為null,即表示是Bootstrap類裝載器。JAVA核心包中有另外兩個類裝載器:ExtClassLoader和AppClassLoader,它們都是用JAVA語言編寫的JAVA類,其中ExtClassLoader類裝載負責載入存放在<JAVA_HOME>/jre/lib/ext目錄下的jar包中的類,AppClassLoader負責載入應用程式的啟動執行類,即當使用java命令去啟動執行一個類時,JAVA虛擬機器使用AppClassLoader載入這個類。在編譯和運行JAVA程式時,都會通過ExtClassLoader類裝載器去<JAVA_HOME>/jre/lib/ext目錄下的JAR包中搜尋要載入的類,所以,如果將包含例如Servlet API的jar包或者是javamail.jar包複製到該目錄下,在編譯Servlet或JavaMail程式時,就不必在CLASSPATH環境變數中增加包含Servlet API的jar包或者是javamail.jar包檔案。
以上,就是一個JAVA程式執行的大致過程。
java不可以運行
開啟Java控制台--->安全
點開編輯網站列表
把你瀏覽的網站貼進去
確定,OK