溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Main方法的執(zhí)行過程是怎樣的

發(fā)布時間:2021-11-12 16:52:22 來源:億速云 閱讀:365 作者:柒染 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)Main方法的執(zhí)行過程是怎樣的,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

一個簡單的Main方法

public class Mm {
        public static void main(String[] args){
            Mm mm = new Mm();
            System.out.println(mm.getClass().getClassLoader());
        }
}

javac Mm.java  
java Mm
這么的話 就進行了一次編譯并執(zhí)行

但是如上執(zhí)行的話我們是沒辦法調(diào)試的,
因此java Mm命令不要直接執(zhí)行,用gdb模式執(zhí)行
所以我們要先編譯一版openJDK,具體編譯OpenJdk代碼過程自行百度,推薦用Windows商店的ubuntu系統(tǒng)編譯

以下是OpenJdk源碼,fork別人的

https://github.com/zscchaofan/openjdk-jdk8u
gdb -q java Mm  //gdb 設置 java 命令
set args Mm  //設置參數(shù)名 具體含義不懂百度搜的
start //啟動調(diào)試

下邊是設置的一些斷點 都是一個一個試出來的 
gdb 可以直接指定文件和行數(shù)打斷點
詳細命令可以百度 我也是百度的就不總結(jié)了 也不常用
調(diào)試代碼如果不參考別人的教程 那就得一步步的走 走幾步
就用gdb 命令查看一下當前代碼上下附近的幾行代碼 再對應到源碼上去看看
像我這不懂c++語言的  只能一步步走 看到方法名意圖很明顯得地方再仔細看
3       breakpoint     keep y   0x00007fffff1e7f4a in JavaMain
                                                   at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c:478
4       breakpoint     keep y   0x00007ffffc97da55 in Java_java_lang_ClassLoader_findBootstrapClass at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:265
9       breakpoint     keep y   0x00007fffff1e9c72 in GetLauncherHelperClass
                                                   at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c:1250
        breakpoint already hit 1 time
14      breakpoint     keep y   0x00007ffffc97da94 in Java_java_lang_ClassLoader_findBootstrapClass at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:272
15      breakpoint     keep y   0x00007ffffc97d3ea in Java_java_lang_ClassLoader_defineClass1
                                                   at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:107

/mnt/d/code/openjdk-jdk8u-master 是我存放代碼的路徑
其實是d盤code下,在ubuntu下加了/mnt

啟動調(diào)試后gdb進入這里會自動停下,這就是最開始的地方
/mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/main.c

main(int argc, char **argv)
{
    .
    .省略一部分代碼 反正也看不懂
    .
    .
    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);
}

繼續(xù)調(diào)試之后找到
/mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c方法,如下
FindBootStrapClass這個方法里查找了jdk里的這個類sun.launcher.LauncherHelper,這個類是c++和java代碼溝通的橋梁了,LauncherHelper實例化時會實例化一個系統(tǒng)類加載器AppClassLoader

if (helperClass == NULL) {
        NULL_CHECK0(helperClass = FindBootStrapClass(env,
                "sun/launcher/LauncherHelper"));
}

之后再去尋找執(zhí)行類的Main方法并執(zhí)行,就是c++調(diào)用java方法,sun.launcher.LauncherHelper#checkAndLoadMain

NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "checkAndLoadMain",
                "(ZILjava/lang/String;)Ljava/lang/Class;"));

因為我們是執(zhí)行java Mm命令,所以很明顯是從Mm類中找到main方法。
其他的比如java -jar 命令還有別的解析方法尋找Main方法

LauncherHelper.checkAndLoadMain 這個方法中會通過Class.forName()查找Mm這個類,根據(jù)雙親委派機制肯定會調(diào)用虛擬機的類加載器

    at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:265
    cls = JVM_FindClassFromBootLoader(env, clname);
    
查看參數(shù) (gdb) p clname
$53 = 0x7fffff7bf3c0 "Mm"

虛擬機返回空

at /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/native/java/lang/ClassLoader.c:272
    if (clname != buf) {
             free(clname);
         }

         return cls;
    }
查看參數(shù) (gdb) p cls
$54 = (jclass) 0x0

所以還是回到了java代碼中的AppClassLoader類加載器中父類URLClassLoader的defineClass方法中去搜索Mm.class,找到之后再去調(diào)用虛擬機方法存儲當前的類

 private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);
看到這里才算明白 
為啥自定義的類加載器加載過指定類之后,new關(guān)鍵字實例化對象時還是會用系統(tǒng)類加載器加載,
new關(guān)鍵字肯定是虛擬機執(zhí)行的 如果自己實現(xiàn)類加載器 加載的類不匯報給虛擬機
那肯定虛擬機是不認可的

在之后虛擬機會真正調(diào)用Mm的Main方法

  /mnt/d/code/openjdk-jdk8u-master/jdk/src/share/bin/java.c
  
  (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

雖然Main方法中有調(diào)用
Mm mm = newMm(); 方法,但是再也沒有走到類加載器,因為之前已經(jīng)加載過了

總結(jié)

  • 1.首先main方法執(zhí)行需要一個操作來啟動,像java Mm這種命令

  • 2.這種命令首先是操作系統(tǒng)解析找到java命令屬于jdk的東西,并調(diào)用jdk的的啟動函數(shù), 就像windows的雙擊操作一樣,雙擊肯定是操作系統(tǒng)搞了什么小動作打開了軟件

  • 3.當操作系統(tǒng)調(diào)用了虛擬機的命令后,虛擬機會拿到命令的參數(shù)比如 Mm,然后去找編譯后的文件

  • 4.虛擬機找到文件后會調(diào)用jdk中的java代碼,找到這個類sun.launcher.LauncherHelper,這個類作為一個工具類,作為橋梁鏈接了c++和java代碼

  • 5.調(diào)用sun.launcher.LauncherHelper類的checkAndLoadMain方法,通過這個方法找執(zhí)行類Mm的Main方法

  • 6.加載好之后執(zhí)行Main

有關(guān)類加載器一個問題

之前想過一個問題就是如何讓new關(guān)鍵字實例化的時候用自定義類加載器?
現(xiàn)在感覺好像無法實現(xiàn),除非替換jdk的類加載器!
//Main
public class CustomerMain {
	public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
		CustomerClassLoader customerClassLoader = new CustomerClassLoader();
		CustomerMain customerMain = (CustomerMain)(customerClassLoader.findClass("CustomerMain").newInstance());
	}
}
//自定義類加載器
class CustomerClassLoader extends ClassLoader{
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {

		try {
			FileInputStream fileInputStream = new FileInputStream("D:\\code\\zerolearnspring\\target\\classes\\cn\\doourbest\\learn\\spring\\zerolearnspring\\controller\\" + name +".class");
			byte[] bb = new byte[fileInputStream.available()];
			int read = fileInputStream.read(bb);
			return defineClass("cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain",bb,0,read);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		throw new ClassNotFoundException("!!");
	}
}

-----console  錯誤信息
Exception in thread "main" java.lang.ClassCastException: cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain cannot be cast to cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain
	at cn.doourbest.learn.spring.zerolearnspring.controller.CustomerMain.main(CustomerMain.java:18)

java虛擬機書中解釋了new對象的過程肯定會先檢查這個指令的參數(shù)能否在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過,如果不存在,再去實行類加載過程。

看完上述內(nèi)容,你們對Main方法的執(zhí)行過程是怎樣的有進一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI