欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

關(guān)于java命令的本質(zhì)邏輯揭秘過程

 更新時(shí)間:2021年05月30日 13:59:07   作者:tera  
Java是通過java虛擬機(jī)來裝載和執(zhí)行編譯文件(class文件)的,java虛擬機(jī)通過命令java  option 來啟動,這篇文章主要給大家介紹了關(guān)于java命令的本質(zhì)邏輯揭秘的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在日常編碼中,有了ide的支持,我們已經(jīng)很少直接在命令行中直接執(zhí)行java XXX命令去啟動一個(gè)項(xiàng)目了。然而我們有沒有想過,一個(gè)簡單的java命令背后究竟做了些什么事情?讓我們看下下面幾個(gè)簡單的問題

1.java命令之后可以跟很多參數(shù),那么這些參數(shù)是如何被解析的?為何-version會返回版本號而如果緊跟一個(gè)類名則會啟動jvm?

2.為何我們自己定義的入口方法必須滿足如下的簽名?是否還有其他可能性?

public static void main(String[] args) {
}

3.如果我們需要調(diào)用自己寫的native方法,必須顯式地通過 System.loadLibrary() 加載動態(tài)鏈接庫。而如果我們查看java的基礎(chǔ)類(Thread、Object、Class等,這些類中有非常多的native方法),則會發(fā)現(xiàn)其內(nèi)部并沒有調(diào)用 System.loadLibrary() 方法,而是由靜態(tài)構(gòu)造函數(shù)中的 registerNatives() 負(fù)責(zé)注冊其它的natvie方法。

例如:Thread.java

class Thread implements Runnable {
    private static native void registerNatives();
    static {
        registerNatives();
    }
    ...
}

不過 registerNatives() 本身也是一個(gè)native方法,那它所在動態(tài)鏈接庫又是何時(shí)被加載的?

問題1和問題2自不必多言,答案一定在java命令中

而對于問題3,因?yàn)門hread、Object、Class等等作為jdk的原生類,其相關(guān)的動態(tài)鏈接庫就是jvm本身(windows系統(tǒng)是 jvm.dll ,linux 系統(tǒng)是libjvm.so,mac 系統(tǒng)是 libjvm.dylib),所以很容易推測其加載動態(tài)鏈接庫的過程一定是在jvm的啟動流程中。

今天我們就以上面3個(gè)問題為引子,探究一下java命令背后的本質(zhì),即jvm的啟動流程

jvm的啟動流程分析

既然需要分析jvm的啟動流程,那么jdk和hotspot的源碼是不可少的。下載地址:http://hg.openjdk.java.net/jdk8

主入口方法

查看 java.c,jdk 目錄 /src/java.base/share/native/libjli,該目錄會因?yàn)椴煌姹镜膉dk有不同

入口方法是 JLI_Launch ,當(dāng)然其中內(nèi)容很多,我們挑選其中的重點(diǎn)部分來看

int
JLI_Launch(args)
{
  ...
  //創(chuàng)建執(zhí)行環(huán)境
  CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               jvmcfg,  sizeof(jvmcfg));
  ...
  //加載jvm
  if (!LoadJavaVM(jvmpath, &ifn)) {
        return(6);
  }
  ...
  //解析命令行參數(shù),例如-h,-version等等
  if (!ParseArguments(&argc, &argv, &mode, &what, &ret, jrepath))
  {
        return(ret);
  }
  ...
  //啟動jvm
  return JVMInit(&ifn, threadStackSize, argc, argv, mode, what, ret);
}

那么接下去就分別查看這幾個(gè)主要方法的邏輯

CreateExecutionEnvironment:創(chuàng)建執(zhí)行環(huán)境

這個(gè)方法根據(jù)操作系統(tǒng)的不同有不同的邏輯,下面以linux系統(tǒng)為例

查看 java_md_solinux.c,jdk 目錄 /src/java.base/unix/native/libjli

CreateExecutionEnvironment(args) {
    /**
     * 獲取jre的路徑
     */
    if (!GetJREPath(jrepath, so_jrepath, JNI_FALSE) ) {
        JLI_ReportErrorMessage(JRE_ERROR1);
        exit(2);
    }
    JLI_Snprintf(jvmcfg, so_jvmcfg, "%s%slib%s%sjvm.cfg",
                    jrepath, FILESEP, FILESEP, FILESEP);
    /**
     * 讀取jvm的版本,這里是根據(jù)jre的路徑,找到j(luò)vm.cfg文件
     */
    if (ReadKnownVMs(jvmcfg, JNI_FALSE) < 1) {
        JLI_ReportErrorMessage(CFG_ERROR7);
        exit(1);
    }

    jvmpath[0] = '\0';
    /**
     * 檢查jvm的版本,如果命令行中有指定,那么會采用指定的jvm版本,否則使用默認(rèn)的
     */
    jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE);
    if (JLI_StrCmp(jvmtype, "ERROR") == 0) {
        JLI_ReportErrorMessage(CFG_ERROR9);
        exit(4);
    }
    /**
     * 獲取動態(tài)鏈接庫的路徑
     */
    if (!GetJVMPath(jrepath, jvmtype, jvmpath, so_jvmpath, 0 )) {
        JLI_ReportErrorMessage(CFG_ERROR8, jvmtype, jvmpath);
        exit(4);
    }
}

主要有以下幾4個(gè)步驟

1.確定jre的路徑

這里會優(yōu)先尋找應(yīng)用程序當(dāng)前目錄

if (GetApplicationHome(path, pathsize)) {
    ...
}

if (GetApplicationHomeFromDll(path, pathsize)) {
    ...
}

2.根據(jù)jre拼接 jvm.cfg 的路徑,并讀取可用的jvm配置

一般 jvm.cfg 文件在 /jre/lib 中,其內(nèi)容如下:

-server KNOWN
-client IGNORE

上述2行配置分別對應(yīng)不同的jvm的版本,例如第一行 -server KNOWN ,那么在加載jvm動態(tài)鏈接庫的時(shí)候就會去 /jre/lib/server 目錄中尋找

3.檢查jvm類型

在執(zhí)行java命令的時(shí)候,可以通過命令指定jvm版本,如果沒有指定,那么就采用jvm.cfg中的第一個(gè)jvm版本

i = KnownVMIndex(arg);
if (i >= 0) {
    ...
}
else if (JLI_StrCCmp(arg, "-XXaltjvm=") == 0 || JLI_StrCCmp(arg, "-J-XXaltjvm=") == 0) {
    ...
}

4.獲取動態(tài)鏈接庫的路徑

根據(jù)前面檢查jvm類型的結(jié)果,獲取到對應(yīng)的jvm動態(tài)鏈接庫的路徑,全部按照默認(rèn)的話,在Mac系統(tǒng)中獲取到的lib路徑如下

路徑中的server正是之前在cfg文件中讀取到的-server

/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home/jre/lib/server/libjvm.dylib

LoadJavaVM:加載jvm

查看 java_md_solinux.c,jdk 目錄 /src/java.base/unix/native/libjli

jboolean
LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{   
    /**
     * 加載動態(tài)鏈接庫,這里調(diào)用的是dlopen,而不是普通的open
     */
    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    ...
    /**
     * 將jvm中的"JNI_CreateJavaVM"方法鏈接到j(luò)dk的CreateJavaVM方法上
     */
    ifn->CreateJavaVM = (CreateJavaVM_t)
        dlsym(libjvm, "JNI_CreateJavaVM");    
    /**
     * 調(diào)用CreateJavaVM方法
     */
    if (ifn->CreateJavaVM == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }
  /**
     * 將jvm中的"JNI_GetDefaultJavaVMInitArgs"方法鏈接到j(luò)dk的GetDefaultJavaVMInitArgs方法上
     */
    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");  
    /**
     * 調(diào)用GetDefaultJavaVMInitArgs方法
     */
    if (ifn->GetDefaultJavaVMInitArgs == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }
  /**
     * 將jvm中的"JNI_GetCreatedJavaVMs"方法鏈接到j(luò)dk的GetCreatedJavaVMs方法上
     */
    ifn->GetCreatedJavaVMs = (GetCreatedJavaVMs_t)
        dlsym(libjvm, "JNI_GetCreatedJavaVMs");  
    /**
     * 調(diào)用GetCreatedJavaVMs方法
     */
    if (ifn->GetCreatedJavaVMs == NULL) {
        JLI_ReportErrorMessage(DLL_ERROR2, jvmpath, dlerror());
        return JNI_FALSE;
    }
}

主要步驟如下:

1.加載動態(tài)鏈接庫,也正是我們第一個(gè)問題的答案所在

dlopen方法是dynamic link open的縮寫,在打開文件的同時(shí),加載動態(tài)鏈接庫??梢酝ㄟ^ man dlopen 命令查看說明

man dlopen
dlopen -- load and link a dynamic library or bundle

2.鏈接并調(diào)用jvm中的 JNI_CreateJavaVM 、GetDefaultJavaVMInitArgs、GetCreatedJavaVMs

dlsym方法是dynamic link symbol的縮寫,將動態(tài)鏈接庫中的方法鏈接到當(dāng)前方法上

man dlsym
dlsym -- get address of a symbol

這3個(gè)方法顧名思義,分別是創(chuàng)建jvm、獲取默認(rèn)的jvm啟動參數(shù)、獲取創(chuàng)建完成的jvm。這3個(gè)方法的入口在

hotspot 目錄 /src/share/vm/prims/jni.cpp

文件中,有興趣的同學(xué)可以自行查看

ParseArguments:解析命令行參數(shù)

查看 java.c,jdk 目錄 /src/java.base/share/native/libjli

static jboolean
ParseArguments(int *pargc, char ***pargv,
               int *pmode, char **pwhat,
               int *pret, const char *jrepath)
{
  ...
  if (JLI_StrCmp(arg, "--version") == 0) {
      printVersion = JNI_TRUE;
      printTo = USE_STDOUT;
      return JNI_TRUE;
  }
  ...
  if (JLI_StrCCmp(arg, "-ss") == 0 ||
              JLI_StrCCmp(arg, "-oss") == 0 ||
              JLI_StrCCmp(arg, "-ms") == 0 ||
              JLI_StrCCmp(arg, "-mx") == 0) {
      char *tmp = JLI_MemAlloc(JLI_StrLen(arg) + 6);
      sprintf(tmp, "-X%s", arg + 1); /* skip '-' */
      AddOption(tmp, NULL);
  }
  ...
}

其中的參數(shù)一共有2大類。

1.類似于 --version 的參數(shù)在解析之后會直接返回

2.類似于 -mx、-mx 的參數(shù)則會通過 AddOption 方法添加成為 VM option

/*
 * Adds a new VM option with the given name and value.
 */
void
AddOption(char *str, void *info)
{
  ...
}

JVMInit:啟動jvm

查看 java_md_solinux.c,jdk 目錄 /src/java.base/unix/native/libjli

JVMInit(InvocationFunctions* ifn, jlong threadStackSize,
        int argc, char **argv,
        int mode, char *what, int ret)
{
    //在一個(gè)新線程中啟動jvm
    return ContinueInNewThread(ifn, threadStackSize, argc, argv, mode, what, ret);
}

在該方法中,會調(diào)用 ContinueInNewThread 創(chuàng)建一個(gè)新線程啟動jvm

查看 java.c,jdk 目錄 /src/java.base/share/native/libjli

int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
                    int argc, char **argv,
                    int mode, char *what, int ret)
{
  ...
  /**
   * 創(chuàng)建一個(gè)新的線程創(chuàng)建jvm并調(diào)用main方法
   */
  rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
  return (ret != 0) ? ret : rslt;
}

在該方法中,會調(diào)用 ContinueInNewThread0 并傳入 JavaMain 入口方法

查看 java_md_solinux.c,jdk 目錄 /src/java.base/unix/native/libjli

/**
 * 阻塞當(dāng)前線程,并在一個(gè)新線程中執(zhí)行main方法
 */
int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
    //創(chuàng)建一個(gè)新線程執(zhí)行傳入的continuation,其實(shí)也就是外面?zhèn)魅氲膍ain方法
    if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
      void * tmp;
      //當(dāng)前線程阻塞
      pthread_join(tid, &tmp);
      rslt = (int)(intptr_t)tmp;
    }
    ...
}

在該方法中,會創(chuàng)建一個(gè)新線程調(diào)用傳入的 main 方法,而當(dāng)前線程則阻塞

因?yàn)檫@里pthread_join是等待在運(yùn)行main方法的線程上,所以java程序運(yùn)行時(shí),如果main線程運(yùn)行結(jié)束了,整個(gè)進(jìn)程就會結(jié)束,而由main啟動的子線程對整個(gè)進(jìn)程是沒有影響的

查看 java.c,jdk 目錄 /src/java.base/share/native/libjli

int JNICALL
JavaMain(void * _args)
{
  //啟動jvm
  if (!InitializeJVM(&vm, &env, &ifn)) {
      JLI_ReportErrorMessage(JVM_ERROR1);
      exit(1);
  }
  ...
  //加載主類
  mainClass = LoadMainClass(env, mode, what);
  //找到main方法id
  mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
                                       "([Ljava/lang/String;)V");
  //通過jni回調(diào)java代碼中的main方法
  (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
}

這里對于main方法的方法名和簽名都是固定判斷的,所以無論是什么java程序,入口方法必須是 public static void main(String[] args)

到此jvm從準(zhǔn)備啟動到最后執(zhí)行main方法的代碼流程就結(jié)束了。因?yàn)檫@個(gè)流程的方法分散在不同的文件中,會很讓人頭暈,所以我總結(jié)了成了以下結(jié)構(gòu),方便大家理解

入口方法:JLI_Launch

        |--------->創(chuàng)建執(zhí)行環(huán)境:CreateExecutionEnvironment

        |          |--------->獲取jre的路徑:GetJREPath

        |          |--------->讀取jvm配置:ReadKnownVMs

        |          |--------->檢查jvm類型:CheckJvmType

        |          |--------->獲取jvm動態(tài)鏈接庫路徑:GetJVMPath

        |--------->加載jvm動態(tài)鏈接庫:LoadJavaVM

        |          |--------->加載動態(tài)鏈接庫:dlopen

        |          |--------->鏈接jvm方法:dlsym

        |--------->解析命令行參數(shù):ParseArguments

        |          |--------->類似于 --version 的參數(shù)在解析之后會直接返回

        |          |--------->類似于 -mx、-mx 的參數(shù)則會通過 AddOption 方法添加成為 VM option

        |--------->啟動jvm并執(zhí)行main方法:JVMInit      

                   |--------->創(chuàng)建一個(gè)新線程并執(zhí)行后續(xù)任務(wù):ContinueInNewThread

                               |--------->創(chuàng)建新線程執(zhí)行main方法:ContinueInNewThread0(JavaMain)

                                    |--------->創(chuàng)建新線程,用于執(zhí)行傳入的main方法:pthread_create

                                    |--------->阻塞當(dāng)前線程:pthread_join

                               |--------->獲取main方法:JavaMain

                                    |--------->加載主類:LoadMainClass

                                    |--------->根據(jù)簽名獲取main方法的id:GetStaticMethodID

                                    |--------->執(zhí)行main方法:CallStaticVoidMethod

總結(jié)

到此這篇關(guān)于關(guān)于java命令的本質(zhì)邏輯揭秘的文章就介紹到這了,更多相關(guān)java命令本質(zhì)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文搞懂Spring中的Bean作用域

    一文搞懂Spring中的Bean作用域

    scope用來聲明容器中的對象所應(yīng)該處的限定場景或者說該對象的存活時(shí)間,即容器在對象進(jìn)入其 相應(yīng)的scope之前,生成并裝配這些對象,在該對象不再處于這些scope的限定之后,容器通常會銷毀這些對象,這篇文章主要介紹了Spring中的Bean作用域,需要的朋友可以參考下
    2022-06-06
  • 聊聊Spring——AOP詳解(AOP概覽)

    聊聊Spring——AOP詳解(AOP概覽)

    這篇文章主要介紹了Spring——AOP詳解(AOP概覽),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 一篇文章教你如何用Java自定義一個(gè)參數(shù)校驗(yàn)器

    一篇文章教你如何用Java自定義一個(gè)參數(shù)校驗(yàn)器

    這篇文章主要介紹了使用java自定義一個(gè)參數(shù)校驗(yàn)器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)
    2021-09-09
  • 基于XML的MyBatis的環(huán)境搭建過程詳解(IDEA)

    基于XML的MyBatis的環(huán)境搭建過程詳解(IDEA)

    這篇文章主要介紹了基于XML的MyBatis的環(huán)境搭建過程詳解(IDEA),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Java中超詳細(xì)this與super的概念和用法

    Java中超詳細(xì)this與super的概念和用法

    這篇文章主要介紹了Java中超詳細(xì)this與super的概念和用法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-02-02
  • JAVA8 lambda表達(dá)式權(quán)威教程

    JAVA8 lambda表達(dá)式權(quán)威教程

    本文主要給大家講解Java8中最重要的一個(gè)特征之一lambda表達(dá)式,本文通過實(shí)例圖文解說給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友跟隨小編一起學(xué)習(xí)下吧
    2021-05-05
  • Java實(shí)現(xiàn)平滑加權(quán)輪詢算法之降權(quán)和提權(quán)詳解

    Java實(shí)現(xiàn)平滑加權(quán)輪詢算法之降權(quán)和提權(quán)詳解

    所有負(fù)載均衡的場景幾乎都會用到這個(gè)平滑加權(quán)輪詢算法,下面這篇文章主要給大家介紹了關(guān)于Java實(shí)現(xiàn)平滑加權(quán)輪詢算法之降權(quán)和提權(quán)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • Java switch支持的數(shù)據(jù)類型詳解

    Java switch支持的數(shù)據(jù)類型詳解

    這篇文章主要介紹了Java switch支持的數(shù)據(jù)類型詳解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 詳解Spring Boot中Controller用法

    詳解Spring Boot中Controller用法

    Controller是SpringBoot里最基本的組件,他的作用是把用戶提交來的請求通過對URL的匹配,分配個(gè)不同的接收器,再進(jìn)行處理,然后向用戶返回結(jié)果。下面通過本文給大家介紹Spring Boot中Controller用法,需要的朋友參考下
    2017-05-05
  • 深入了解Java中的反射機(jī)制(reflect)

    深入了解Java中的反射機(jī)制(reflect)

    Java的反射機(jī)制允許我們對一個(gè)類的加載、實(shí)例化、調(diào)用方法、操作屬性的時(shí)期改為在運(yùn)行期進(jìn)行,這大大提高了代碼的靈活度,本文就來簡單講講反射機(jī)制的具體使用方法吧
    2023-05-05

最新評論