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

  今天上午,主要將昨天下午運行機制中,jvm的launch方法的內容詳細講述一下。 在vm的launch中,有如下方法塊:

....
HANDLE cur_handle = (HANDLE)(_beginthreadex(NULL, 0, scapegoat, &p, 0, NULL));
    this->tid = GetThreadId(cur_handle);		// save to the vm_thread.
    if (!inited) {		// if this is the main thread which create the first init --> thread[0], then wait.
        //todo: 阻塞執行 tid線程,tid執行完後才往後執行
        WaitForSingleObject(cur_handle,INFINITE);
....

  其中第一行代碼開闢的這個線程,便是java中的mian線程,也就是java中的主線程。 它接收命令的參數去執行任務。  其中,scapegoat方法 的代碼如下:

//線程的任務
unsigned scapegoat (void *pp) {
    temp *real = (temp *)pp;
//	if (real->cur_thread_obj != nullptr) {		// so the ThreadTable::get_thread_obj may be nullptr.		// I add all thread into Table due to gc should stop all threads.
    ThreadTable::add_a_thread(GetCurrentThreadId(), real->cur_thread_obj, real->thread);		// the cur_thread_obj is from `java/lang/Thread.start0()`.
//	}
    if (real->should_be_stop_first) {		// if this thread is a child thread created by `start0`: should stop it first because of gc's race.
        // it will be hung up at the `global pthread_cond`. and will be wake up by `signal_all_thread()`.
        wait_cur_thread_and_set_bit(&real->the_first_wait_executed, real->thread);
    }
    real->thread->start(*real->arg);
    return 0;
};

  通過代碼不難察覺,它首先將當前的線程加入了jvm的線程表中,進行管理(注意,此時jvm線程表中,實際上管理的線程有兩個了,一個是init_thread,它是本地線程的抽象,另一個就是 mian線程,也就是當前加入的這個線程)。  接着,當前線程(mian線程) 將會調用 start()方法,將命令行的參數一併傳遞。 start的源碼如下:

void vm_thread::start(list<Oop *> & arg)
{
    if (automan_jvm::inited() == false) {
        assert(method == nullptr);			// if this is the init thread, method will be nullptr. this thread will get `main()` automatically.
        assert(arg.size() == 0);
        automan_jvm::inited() = true;			// important!
        //todo: 這裏是執行 main 方法  ,重要
        vm_thread::init_and_do_main();		// init global variables and execute `main()` function.
    } else {
        // [x] if this is not the thread[0], detach itself is okay because no one will pthread_join it.
        //todo: 這裏分離子線程
//        CloseHandle(tid);
//        pthread_detach(pthread_self());
        assert(this->vm_stack.size() == 0);	// check
        assert(arg.size() == 1);				// run() only has one argument `this`.
        this->vm_stack.push_back(StackFrame(method, nullptr, nullptr, arg, this));
        this->execute();
        automan_jvm::num_lock().lock();
        {
            automan_jvm::thread_num() --;
            assert(automan_jvm::thread_num() >= 0);
        }
        automan_jvm::num_lock().unlock();
    }
    WaitForSingleObject(_all_thread_wait_mutex,INFINITE);
    this->state = Death;
    ReleaseMutex(_all_thread_wait_mutex);
}

   它會根據jvm是否初始化而判定當前線程的start是去引導和啓動main方法,還是一般的線程。 注意我們在學習java的時候,實例化線程我們需要重寫run(){}方法,這個run方法裏面寫的實際上是線程的任務,而線程的啓動,是由一個start0本地方法,即jvm調用的。 調用之後,會來到這裏這個代碼塊。  它將走else下面這個代碼邏輯,裏面實際上就是執行了run方法。   run方法執行完後,會將當前的線程狀態改爲 death.   之後這個線程便會在特定的時期被gc給回收掉。

   扯遠了,我們還是看看main線程的操作吧。 它將會調用 vm_thread::init_and_do_main()方法。 這個方法就比較長,我將分塊展示。

1.初始化Class,用於類的映射

java_lang_class::init();		// must init !!!
        auto class_klass = BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/Class");
        java_lang_class::fixup_mirrors();	// only [basic types] + java.lang.Class + java.lang.Object

  首先,調用java_lang_class::init()方法,它的作用是將 使用標識符與 (核心)類進行隱射,代碼如下:

void java_lang_class::init() {		// must execute this method before jvm!!!
    auto & delay_mirrors = get_single_delay_mirrors();
    // basic types.
    delay_mirrors.push(L"I");
    delay_mirrors.push(L"Z");
    delay_mirrors.push(L"B");
    delay_mirrors.push(L"C");
    delay_mirrors.push(L"S");
    delay_mirrors.push(L"F");
    delay_mirrors.push(L"J");
    delay_mirrors.push(L"D");
    delay_mirrors.push(L"V");	// void...
    delay_mirrors.push(L"[I");
    delay_mirrors.push(L"[Z");
    delay_mirrors.push(L"[B");
    delay_mirrors.push(L"[C");
    delay_mirrors.push(L"[S");
    delay_mirrors.push(L"[F");
    delay_mirrors.push(L"[J");
    delay_mirrors.push(L"[D");
    // set state
    state() = Inited;
}

  然後,使用BootstrapClassLoader去加載 Class類,注意注意,我們看下BoostrapClassLoader的源碼:

class BootStrapClassLoader : public ClassLoader {
private:
    JarLister jl;
private:
    BootStrapClassLoader() {}
    BootStrapClassLoader(const BootStrapClassLoader &);
    BootStrapClassLoader& operator= (const BootStrapClassLoader &);
    ~BootStrapClassLoader() {}
public:
    static BootStrapClassLoader & get_bootstrap() {
        static BootStrapClassLoader bootstrap;
        return bootstrap;
    }	// singleton
    Klass *loadClass(const wstring & classname, ByteStream * = nullptr, MirrorOop * = nullptr,
                     bool = false, InstanceKlass * = nullptr, ObjArrayOop * = nullptr) override;
    void print() override;
    void cleanup() override;
};

   從接口中,我們能夠判斷兩條消息:

      1.get_bootstrap調用會得到一個單例對象; 

      2. BootstrapClassLoader有一個成員變量JarLister,首次調用時,會觸發它的構造方法,我們去看看它的構造方法:

2.初始化BootstrapClassloader以及加載Class:

//todo: 修改 rjd 爲windows 的路徑
JarLister::JarLister() : rjd(L"")
{
    pwd = utf8_to_wstring(getProgramDir());
    rjd = RtJarDirectory(pwd);
    wstring rtjar_folder;
#if (defined (__APPLE__))
    rtjar_folder = utf8_to_wstring(pt.get<std::string>("path.mac"));
#elif (defined (__linux__))
    rtjar_folder = utf8_to_wstring(pt.get<std::string>("path.linux"));
#else
    //todo: 這裏配置 windows的 rt路徑
    rtjar_folder = utf8_to_wstring("C:\\Program Files\\Java\\jdk1.8.0_161\\jre\\lib\\");
#endif
    rtjar_pos =L"\""+ rtjar_folder + L"rt.jar"+L"\"";
    // copy lib/currency.data to ./lib/currency.data ......
    wstringstream ss;
    int status = system(wstring_to_utf8(ss.str()).c_str());
    if (status == -1) {  	// http://blog.csdn.net/cheyo/article/details/6595955 [shell 命令是否執行成功的判定]
        std::cerr << "system error!" << endl;
    }
    bool success = this->getjarlist(rtjar_pos);
    if (!success)	exit(-1);
    ifstream f(wstring_to_utf8(this->rtlist), std::ios_base::in);
    std::string s;
    while(!f.eof()) {
        f >> s;		// 這裏有一個細節。因爲最後一行僅僅有個回車,所以會讀入空,也就是 s 還是原來的 s,即最後一個名字被讀入了兩遍。使用其他的方法對效率不好,因此在 add_file 中解決了。如果檢測到有,忽略。
        if (!Filter::filt(utf8_to_wstring(s))) {
            this->rjd.add_file(StringSplitter(utf8_to_wstring(s)));
        }
    }
}

   通過代碼可以分析,它做了這樣的事情: 

        1. 或許到當前環境的 rt.jar,這個文件時jvm的核心jar包,我配置的是我本機環境的rt.jar,它的版本號是: jdk_1.8_161。同時,因爲原來這裏使用了配置的方式,但是需要以來boost,我就給替換了,直接手寫死的。  

        2.調用 getjartlist方法,該方法馬上詳述。

        3.調用rjd的add_file方法。 也將會詳述。

   下面是getjarlist方法:

/*===---------------- JarLister --------------------*/
bool JarLister::getjarlist(const wstring & rtjar_pos) const
{
    wstringstream cmd;
    cmd << L"jar tf " << rtjar_pos << L" > " << this->rtlist;
    int status =  system(wstring_to_utf8(cmd.str()).c_str());
    if (status == -1) {
        exit(-1);
    }
    // TODO: judge whether mkdir is exist?
    if (0==access(wstring_to_utf8(uncompressed_dir).c_str(),F_OK)) {	// 如果存在
        return true;
    }
    cmd.str(L"");
    cmd << L"mkdir " << uncompressed_dir;
    status = system(wstring_to_utf8(cmd.str()).c_str());
    if (status == -1) {
        exit(-1);
    }
    cmd.str(L"");
    std::wcout << "unzipping rt.jar from: [" << rtjar_pos << "] ... please wait.\n";
    cmd << L"unzip " << rtjar_pos << L" -d " << uncompressed_dir ;
    status = system(wstring_to_utf8(cmd.str()).c_str());
    if (status == -1) {  	// http://blog.csdn.net/cheyo/article/details/6595955 [shell 命令是否執行成功的判定]
        std::cerr << "system error!" << endl;
        exit(-1);
    } else {
        if (status) {
            if (0 ==status) {
                std::wcout << "unzipping succeed.\n";
                return true;
            }
            else {
                std::cerr << "Your rt.jar file is not right!" << endl;
            }
        } else {
            std::cerr << "other fault reasons!" << endl;
        }
    }
    return false;
}

    它做了這樣的事情:

           1.通過jar tf 將rt.jar保存的所有類 保存至某一特定文件中;

           2.將rt.jar解壓至某一個特定文件夾中;(注意,這個文件夾將會作爲是否解壓的標準,在同一進程中,最多隻可能被加壓一次。 我當前版本加壓出來有 2999個類,如果解壓信息輸出到控制檯的話,還是要費點時間。但是我想在發行版中,一個環境下的jvm,應該只被解壓一次)。

   接着是rtjarDirectory的addfile()方法:

void RtJarDirectory::add_file(StringSplitter && ss)
{
    if (ss.counter() == 0) {		// 僅僅在第一次的時候做檢查,看文件到底存不存在
        if (this->find_file(std::move(ss)) == true) return;
        else ss.counter() = 0;
    }
    const wstring& target = ss.result()[ss.counter()];
    if (ss.counter() == ss.result().size() - 1) {	// next will be the target, add.
        subdir->insert(make_shared<RtJarDirectory>(target));
    } else {	// dir.
        auto next_dir = findFolderInThis(target);
        ss.counter() += 1;
        if (next_dir != nullptr) {
            (*next_dir).add_file(std::move(ss));	// delegate to the next level dir.
        } else {	// no next_dir, we should create.
            // this level's `subdir` can't be nullptr :)
            subdir->insert(make_shared<RtJarDirectory>(target));
            next_dir = findFolderInThis(target);
            assert(next_dir != nullptr);
            (*next_dir).add_file(std::move(ss));
        }
    }
}

   它會將 保存的所有的類,通過根據全限定名稱(包名+類名)的形式,進行分割,最終將 類名(去除了包名)的名稱 保存進一個智能指針。  這個結構頗爲複雜,我沒太看懂,它的定義是這樣的:

 shared_ptr<set<shared_ptr<RtJarDirectory>,shared_RtJarDirectory_compare>> subdir; //sub directory

  我暫且先理解爲它保存了所有的類名稱在內存中吧,注意到我獲取的類的條目有一萬多:

  BootstrapClassLoader初始化完成後,就該去loadClass了,以加載Class爲例:

// add lock simply
    LockGuard lg(system_classmap_lock);
    assert(jl.find_file(L"java/lang/Object.class")==1);
    wstring target = classname + L".class";
    if (jl.find_file(target)) {
        if (system_classmap.find(target) != system_classmap.end()) {	// has been loaded
            return system_classmap[target];
        } else {	// load
            // parse a ClassFile (load)
            ifstream f(wstring_to_utf8(jl.get_sun_dir() + L"/" + target).c_str(), std::ios::binary);
            if(!f.is_open()) {
                std::wcerr << "wrong! --- at BootStrapClassLoader::loadClass" << std::endl;
                exit(-1);
            }
#ifdef DEBUG
            sync_wcout{} << "===----------------- begin parsing (" << target << ") 's ClassFile in BootstrapClassLoader..." << std::endl;
#endif
            ClassFile *cf = new ClassFile;
            ClassFile_Pool::put(cf);
            f >> *cf;
#ifdef DEBUG
            sync_wcout{} << "===----------------- parsing (" << target << ") 's ClassFile end." << std::endl;
#endif
            // convert to a MetaClass (link)
            InstanceKlass *newklass = new InstanceKlass(cf, nullptr);
            system_classmap.insert(make_pair(target, newklass));
#ifdef KLASS_DEBUG
            BootStrapClassLoader::get_bootstrap().print();
	        MyClassLoader::get_loader().print();
#endif
            return newklass;
        }
        //todo:equals to starts with
    }
.........

   上面爲loadClass代碼片段,可以看到第二行,它首先判斷了java.lang.Object.class,通過前面的那個智能指針,顯然Object是在裏面的。(但是,此時Object並未加載。)這也印證了我前天文章中所說的,Object的唯一性是必須要首先保證的。 同時,加載成功時,會首先 實例一個ClassFile放入 類池,這個類對象保存的是  字節碼二進制流!!! 接着,實例化一個  實例類對象,將該類對象保存進入  system_classmap。 

3.設置Object,以及基本類型的單例映像:

  該步驟是通過  Class完成的。 

java_lang_class::fixup_mirrors();	// only [basic types] + java.lang.Class + java.lang.Object

  該方法將  8個基本類型,和void 以Mirror實例的形式放入了一個緩存。

......
switch (name[0]) {
                case L'I':case L'Z':case L'B':case L'C':case L'S':case L'F':case L'J':case L'D':case L'V':{	// include `void`.
                    // insert into.
                    MirrorOop *basic_type_mirror = ((MirrorKlass *)klass)->new_mirror(nullptr, nullptr);
                    basic_type_mirror->set_extra(name);			// set the name `I`, `J` if it's a primitve type.
                    get_single_basic_type_mirrors().insert(make_pair(name, basic_type_mirror));
                    break;
                }
                default:{
                    assert(false);
                }
            }
......

4.加載String及Thread:

 // load String.class
        auto string_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/String"));


        // 1. create a [half-completed] Thread obj, using the ThreadGroup obj.(for currentThread(), this must be create first!!)
        auto thread_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/Thread"));
InstanceOop *init_thread = thread_klass->new_instance();
        BytecodeEngine::initial_client(thread_klass, *this);		// first <clinit>!
        // inject!!
        //todo: 注意這裏的 threadid 的來源,可能要修改
        init_thread->set_field_value(THREAD L":eetop:J", new LongOop((uint64_t)GetCurrentThreadId()));
        //todo: 這裏的線程優先級還沒有綁定到 thread句柄上, 通過 setThreadPriority
        init_thread->set_field_value(THREAD L":priority:I", new IntOop(5));
        //todo: 這裏通過 openthread 根據當前線程的id 獲取到線程句柄  注意,當前線程的句柄不能關閉
        ThreadTable::add_a_thread(GetCurrentThreadId(), init_thread, this);

   加載完Thread之後,會生成一個Thread的實例對象。之後會進行類的初始化,注意這是jvm中第一次類的初始化,它會一直向上初始化直到Object。

   設置相關屬性後,放入jvm的線程表中。 注意了,此時有在線程表中,就有了兩個個線程了。 但是注意這個線程跟第二個線程的id號是一致的,同時,它並不是真正意義上的線程,只是一個抽象。  在線程表的插入中有如下代碼:

 if (get_thread_table().insert(make_pair(tid, make_tuple(get_thread_table().size(), _thread, t))).second == false) {	// 如果原先已經插入了的話,那麼就複用原先的 thread_no.
        number = std::get<0>(get_thread_table()[tid]);
    }

     也就是說,the_whole_world的線程表中的線程數仍然是兩個。 

5.加載並實例化ThreadGroup:

 // 2. create a [System] ThreadGroup obj.
        auto threadgroup_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/ThreadGroup"));
        InstanceOop *init_threadgroup = threadgroup_klass->new_instance();
        BytecodeEngine::initial_client(threadgroup_klass, *this);		// first <clinit>!
        {
            std::list<Oop *> list;
            list.push_back(init_threadgroup);	// $0 = this
            // execute method: java/lang/ThreadGroup.<init>:()V --> private Method!!
            Method *target_method = threadgroup_klass->get_this_class_method(L"<init>:()V");
            assert(target_method != nullptr);
            this->add_frame_and_execute(target_method, list);
        }
// 3. INCOMPLETELY create a [Main] ThreadGroup obj.
        InstanceOop *main_threadgroup = threadgroup_klass->new_instance();
        {
            init_thread->set_field_value(THREAD L":group:Ljava/lang/ThreadGroup;", main_threadgroup);
        }
        assert(this->vm_stack.size() == 0);

        BytecodeEngine::initial_client(((InstanceKlass *)class_klass), *this);
        ((InstanceKlass *)class_klass)->set_static_field_value(L"useCaches:Z", new IntOop(false));

    注意,這裏除了 類初始化之外,還初始化了一個 ThreadGroup對象。 實例化的threadGroup爲主線程組。 

6.加載並初始化System:

// 3. load System class
        auto system_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/System"));
        system_klass->set_state(Klass::KlassState::Initializing);
//		BytecodeEngine::initial_clinit(system_klass, *this);
        auto InputStream_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/io/InputStream"));
        BytecodeEngine::initial_client(InputStream_klass, *this);
        auto PrintStream_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/io/PrintStream"));
        BytecodeEngine::initial_client(PrintStream_klass, *this);
        auto SecurityManager_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/SecurityManager"));
        BytecodeEngine::initial_client(SecurityManager_klass, *this);

    System用於設置相關的屬性,並且完成 當前程序的標準輸入流,輸出流,錯誤流的綁定。 windows上,每個程序的標準輸入輸出和錯誤流就是控制檯。 

7.加載並實例化Perf 和 LauncherHelper:

  auto Perf_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"sun/misc/Perf"));
    Perf_klass->set_state(Klass::KlassState::Initializing);				// ban Perf.
    auto PerfCounter_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"sun/misc/PerfCounter"));
    PerfCounter_klass->set_state(Klass::KlassState::Initializing);		// ban PerfCounter.

    auto launcher_helper_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"sun/launcher/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;");

  Perf作用暫時不清楚,LauncherHelper會用於引導 Launcher然後使用 AppClassLoader去加載用戶類。

8.加載動態調用的類:

 // load some useful klass...
    {
        auto methodtype_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/invoke/MethodType"));
        BytecodeEngine::initial_client(methodtype_klass, *this);
        auto methodhandle_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/invoke/MethodHandle"));
        BytecodeEngine::initial_client(methodhandle_klass, *this);

    }

9.執行main:

launch流程結束。


 沒想到內容還是有些多,有點招架不住啊。   中間細節還有: system的初始化,以及java的雙親類加載機制,並沒有寫。  下一篇吧,還涉及到文件描述符啥啥啥的。

  苦膽都要給我幹出來了,加油! 必須喫透。

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