windows系統使用c++實現一個小型jvm(三)------------jvm的啓動細節2

  這篇文章接着上午記錄下。 

1.標準輸入輸出流是怎麼來的?

   在寫java程序的時候,我們經常輸出控制檯信息,調用的如下代碼: System.out.Println();  在這裏,我將解釋這個的由來。

   jvm在初始化時,必須先加載FileDescriptor,FileDescriptor有三個靜態成員: 

  它們會調用本地FileDescriptor的SetI方法:

void JVM_FD_Set(list<Oop *>& _stack){
    //todo: 這裏是初始化FileDescriptors時,會將標準輸入輸出,以及錯誤流 進行與FileOutputStream綁定
    IntOop *fd = (IntOop *)_stack.front();	_stack.pop_front();
    HANDLE ret;
    if(fd->value==0){//標準輸入流
        ret= GetStdHandle(STD_INPUT_HANDLE);
    }else if(fd->value==1){//標準輸出流
        ret= GetStdHandle(STD_OUTPUT_HANDLE);
    }else{//標準錯誤流
        ret= GetStdHandle(STD_ERROR_HANDLE);
    }
    long addr = HandleToLong(ret);//ret爲指針類型,該指針類型指向long值,*爲取出該值
    _stack.push_back(new LongOop(addr));
}

  windows中,每個程序的標準輸入輸出流和錯誤流,爲當前程序的控制檯。  這裏實際上就是控制檯程序的句柄給設置到相應的對象中。  之後再初始化System的時候,會有如下的代碼片段: 

   而實際上這個setIn0,setOut0,setErr0,就是將輸入輸出流句柄,給設置到 System.in,或者System.out,System.error屬性上去,其代碼如下:

void JVM_SetOut0(list<Oop *> & _stack){		// static
    InstanceOop *printstream = (InstanceOop *)_stack.front();	_stack.pop_front();
    auto system = BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/System");
    assert(system != nullptr);
    ((InstanceKlass *)system)->set_static_field_value(L"out:Ljava/io/PrintStream;", printstream);
}

  另外,從這個流程中,我想起來大概兩年前寫 看java源碼的 流 部分的時候,曾經總結過,說: java中真正更夠讀寫的就兩個流,一個是FileInputStream/FileOutputStream ,另一個是ArrayIntputStream/ArrayOutputStream。   知識誠不欺我呀哈哈哈哈。 

2.java的雙親類加載機制: 

   說到這個古老的話題,那可得追溯到我寫文章開始。 那一年春節,我決定看看源碼,一上來就猛的 想把那個雙親加載機制給搞明白。 事實上,一直沒怎麼搞明白,唯一的作用是給了自己學習的動力。  但是今天我想借着這個機會,是可以完全搞懂的。

   上午說到,jvm在加載客戶類之前,會啓動一個LauncherHelper類,代碼如下:

 BytecodeEngine::initial_client(launcher_helper_klass, *this);
    Method *load_main_method = launcher_helper_klass->get_this_class_method(L"checkAndLoadMain:(ZILjava/lang/String;)Ljava/lang/Class;");
    // new a String.
    wstring ss = automan_jvm::main_class_name();
    InstanceOop *main_klass = (InstanceOop *)java_lang_string::intern(automan_jvm::main_class_name());

    this->vm_stack.push_back(StackFrame(load_main_method, nullptr, nullptr, {new IntOop(true), new IntOop(1), main_klass}, this));
    //todo: 到這裏應該是java層面的類加載器開始生效!!!
    MirrorOop *main_class_mirror = (MirrorOop *)this->execute();

     接着去看看 checkAndLoadMain方法:

  之後通過ClassLoader的loadClass方法,通過查找虛擬表,調用Launcher的AppClassLoader的loadClass方法,再調用之前,將會首先進行AppClassClassloader的初始化(這個初始化過程蠻複雜的):

  花了較多的精力去看這塊的源碼,主要原因基於以下幾點: 

     1.windows中由於文件系統路徑分隔符的原因,我在調試時就遇到了一個坑,即我明明需要使用 文件系統去加載,但是它卻使用了JarLoader去加載。   現在回過頭來,知道了其原因: 首先在LauncherHelper中會根據模式選擇加載器,其次會判斷java.class.path下面的所有配置路徑,如果爲文件夾,則會匹配爲 fileLoader,如果爲文件,則會使用默認的loader,而實際上默認的loader,其最終採用的還是Jarloader的方式加載的。(我的問題就出在這!)。

     2.AppClassLoader與ExtClassLoader都繼承自URLClasspath,因此必須弄明白URLClasspath是在什麼時機初始化的,以及相應的參數都是什麼。  關於URLClasspath的作用,網上帖子挺多。 它的幾個關鍵方法: loadClass,findClass,defineClass 可以用於驗證雙親委派機制的運行流程。 

  我在調試時,分別給loadClass 和  defineClass添加了錨點, 最終實際上是可以印證雙親委派模型的。  由於當時未截圖,因此這裏就不再繼續操作了。(因爲這個過程實在是有些痛苦~,感興趣的小夥伴可以親自調試一下)。

  另外強調一點就是,以前沒有認識到URLClassLoader在java中的重要性,以及URLClassLoader與URLClasspath的關係,這是一個十分有趣的問題。   

   此外,在AppclassLoader加載類的過程中,它的流將會以匿名內部類的形式給出: 


  就到這吧,原計劃寫的很詳細的,但是真寫了太細了吧,速度太慢,自己又是個急性子。  whatever,後面趕緊把gc寫了要開始投入新一輪的學習,同時得計劃找工作了55555。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章