main函数初探
main方法初探
- 題外話
- main函數(shù)的類(lèi)是怎么被虛擬機(jī)識(shí)別加載至內(nèi)存的
- jvm的運(yùn)行機(jī)制
- jvm的啟動(dòng)細(xì)節(jié)1---launch
- jvm的啟動(dòng)細(xì)節(jié)2---classloader
題外話
剛學(xué)java的同學(xué)肯定都知道m(xù)ain方法是一個(gè)程序的入口,為我們創(chuàng)建了一個(gè)主線程,作為一個(gè)老油條了,今天學(xué)習(xí)springboot項(xiàng)目啟動(dòng)時(shí)發(fā)現(xiàn)也是通過(guò)main方法啟動(dòng)的,于是就觸發(fā)了我的好奇心,main函數(shù)是怎么被執(zhí)行的呢?被執(zhí)行之前虛擬機(jī)幫我們做了些什么呢?
想了解springboot啟動(dòng)流程和autoconfig的同學(xué)請(qǐng)看這里:
main函數(shù)的類(lèi)是怎么被虛擬機(jī)識(shí)別加載至內(nèi)存的
java編譯成class文件后,虛擬機(jī)是怎么解析class文件并且初始化我們的實(shí)體類(lèi)的呢? 這時(shí)候提到j(luò)vm類(lèi)加載機(jī)制和雙親委派模型了
1)Bootstrap ClassLoader
負(fù)責(zé)加載$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++實(shí)現(xiàn),不是ClassLoader子類(lèi)
2)Extension ClassLoader
負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包
3)App ClassLoader
負(fù)責(zé)記載classpath中指定的jar包及目錄中class
4)Custom ClassLoader
屬于應(yīng)用程序根據(jù)自身需要自定義的ClassLoader,如tomcat、jboss都會(huì)根據(jù)j2ee規(guī)范自行實(shí)現(xiàn)ClassLoader
加載過(guò)程中會(huì)先檢查類(lèi)是否被已加載,檢查順序是自底向上,從Custom ClassLoader到BootStrap ClassLoader逐層檢查,只要某個(gè)classloader已加載就視為已加載此類(lèi),保證此類(lèi)只所有ClassLoader加載一次。而加載的順序是自頂向下,也就是由上層來(lái)逐層嘗試加載此類(lèi)。
那么我們自己在項(xiàng)目里面寫(xiě)的代碼是由哪個(gè)加載器加載的呢?
雙親委派
雙親委派模式要求除了頂層的啟動(dòng)類(lèi)加載器之外,其余的類(lèi)加載器都應(yīng)該有自己的父類(lèi)加載器,但是在雙親委派模式中父子關(guān)系采取的并不是繼承的關(guān)系,而是采用組合關(guān)系來(lái)復(fù)用父類(lèi)加載器的相關(guān)代碼。
工作原理
如果一個(gè)類(lèi)收到了類(lèi)加載的請(qǐng)求,它并不會(huì)自己先去加載,而是把這個(gè)請(qǐng)求委托給父類(lèi)加載器去執(zhí)行,如果父類(lèi)加載器還存在父類(lèi)加載器,則進(jìn)一步向上委托,依次遞歸,請(qǐng)求最后到達(dá)頂層的啟動(dòng)類(lèi)加載器,如果弗雷能夠完成類(lèi)的加載任務(wù),就會(huì)成功返回,倘若父類(lèi)加載器無(wú)法完成任務(wù),子類(lèi)加載器才會(huì)嘗試自己去加載,這就是雙親委派模式。就是每個(gè)兒子都很懶,遇到類(lèi)加載的活都給它爸爸干,直到爸爸說(shuō)我也做不來(lái)的時(shí)候,兒子才會(huì)想辦法自己去加載。
優(yōu)勢(shì)
采用雙親委派模式的好處就是Java類(lèi)隨著它的類(lèi)加載器一起具備一種帶有優(yōu)先級(jí)的層次關(guān)系,通過(guò)這種層級(jí)關(guān)系可以避免類(lèi)的重復(fù)加載,當(dāng)父親已經(jīng)加載了該類(lèi)的時(shí)候,就沒(méi)有必要子類(lèi)加載器(ClassLoader)再加載一次。其次是考慮到安全因素,Java核心API中定義類(lèi)型不會(huì)被隨意替換,假設(shè)通過(guò)網(wǎng)路傳遞一個(gè)名為java.lang.Integer的類(lèi),通過(guò)雙親委派的的模式傳遞到啟動(dòng)類(lèi)加載器,而啟動(dòng)類(lèi)加載器在核心Java API發(fā)現(xiàn)這個(gè)名字類(lèi),發(fā)現(xiàn)該類(lèi)已經(jīng)被加載,并不會(huì)重新加載網(wǎng)絡(luò)傳遞過(guò)來(lái)的java.lang.Integer.而之際返回已經(jīng)加載過(guò)的Integer.class,這樣便可以防止核心API庫(kù)被隨意篡改。可能你會(huì)想,如果我們?cè)赾alsspath路徑下自定義一個(gè)名為java.lang.SingInteger?該類(lèi)并不存在java.lang中,經(jīng)過(guò)雙親委托模式,傳遞到啟動(dòng)類(lèi)加載器中,由于父類(lèi)加載器路徑下并沒(méi)有該類(lèi),所以不會(huì)加載,將反向委托給子類(lèi)加載器,最終會(huì)通過(guò)系統(tǒng)類(lèi)加載器加載該類(lèi),但是這樣做是不允許的,因?yàn)閖ava.lang是核心的API包,需要訪問(wèn)權(quán)限,強(qiáng)制加載將會(huì)報(bào)出如下異常。
想了解更多關(guān)于jvm類(lèi)加載機(jī)制和雙親委派模型可以參考:
jvm的運(yùn)行機(jī)制
開(kāi)始進(jìn)入正題,讓我們一起看看底層C++代碼是怎么一步一步執(zhí)行到main函數(shù)的吧
main()方法:
int main(int argc, char *argv[]) { //#ifdef DEBUGsync_wcout::set_switch(true); //#endifif (argc != 2) {std::wcerr << "argc is not 2. please re-run." << std::endl;exit(-1);}wstring program = utf8_to_wstring(std::string(argv[1]));std::ios::sync_with_stdio(true); // keep thread safe?std::wcout.imbue(std::locale(""));std::vector<std::wstring> v{ L"automan_jvm", L"1", L"2" };automan_jvm::run(program, v); }從源碼中,我們可以看到,它從程序啟動(dòng)時(shí)獲取參數(shù),并進(jìn)行監(jiān)測(cè)。 目前該程序要求的是只能有兩個(gè)參數(shù)。 但是實(shí)際上還有許多參數(shù)是可以在這里配置的 ,參數(shù)設(shè)置請(qǐng)看這里 JVM參數(shù)設(shè)置
run()方法:
然后,它以相關(guān)的參數(shù),調(diào)用了 jvm的 run方法。 代碼如下:
注意到代碼的第二行,它注冊(cè)了一個(gè)交互信號(hào),此時(shí)的環(huán)境仍然是本地線程,即該線程為根線程,不受jvm的管理。信號(hào)處理程序?qū)嶋H上是一個(gè) gc任務(wù),這也是為什么我們時(shí)常聽(tīng)到j(luò)ava的gc觸發(fā),既有jvm管理的部分,也有本地的部分。 它的代碼如下:
void SIGINT_handler(int signo) {// re-use gc bit to stop-the-world,but won't trigger GC。while (true) {bool gc;GC::gc_lock().lock();{gc = GC::gc();}GC::gc_lock().unlock();if (gc) {continue;} else {GC::gc_lock().lock();{GC::gc() = true;}GC::gc_lock().unlock();// FIXME: I don't know whether it is safe... only a solution for dead lock of wind_jvm::num_lock...automan_jvm::num_lock().unlock(); // It's only a patch.GC::detect_ready();GC::gc() = false; // set backBytecodeEngine::main_thread_exception(); // exit}} }我們觀察到,該方法實(shí)際上是一個(gè)無(wú)限循環(huán),但是這個(gè)線程又是根線程,所以我最開(kāi)始有一些疑惑。 現(xiàn)在回頭來(lái)看時(shí),我注意到: GC::detect_ready(),該方法實(shí)際上也是無(wú)限循環(huán)的,但是,它會(huì)主動(dòng)的釋放cpu。 我們看看這個(gè)方法:
void GC::detect_ready() {while (true) {LockGuard lg(gc_lock());int total_ready_num = 0;int total_size;ThreadTable::get_lock().lock();{total_size = ThreadTable::get_thread_table().size();for (auto & iter : ThreadTable::get_thread_table()) {thread_state state = std::get<2>(iter.second)->state;if (state == Waiting || state == Death/*iter.second == false && iter.first->vm_stack.size() == 0*/) {total_ready_num ++;} else {break;}}}ThreadTable::get_lock().unlock();ThreadTable::print_table(); // deleteif (total_ready_num == total_size) { // over!return;}Sleep(1);} }可以看到,它實(shí)際上在管理 jvm內(nèi)部的線程。 根據(jù)線程狀態(tài),進(jìn)行線程的回收。 它的退出條件是: jvm的內(nèi)部所有線程均為活躍線程。 同時(shí),它沒(méi)有互斥量進(jìn)行阻塞,可見(jiàn)它的活躍程度是很高的。 換言之,一旦程序觸發(fā)了退出信號(hào),jvm內(nèi)部的線程維護(hù),幾乎時(shí)刻在運(yùn)行。
讓我們回到 信號(hào)處理程序上,它最后調(diào)用了: BytecodeEngine::main_thread_exception(),它的代碼及作用如下:
void BytecodeEngine::main_thread_exception(int exitcode) // dummy is use for BytecodeEngine::excute / SIGINT_handler. {automan_jvm::lock().lock();{for (auto & thread : automan_jvm::threads()) {WaitForSingleObject(_all_thread_wait_mutex,INFINITE);thread_state state = thread.state;ReleaseMutex(_all_thread_wait_mutex);if (state == Death) { // pthread_cancel SIGSEGV bug sloved:continue;}if (thread.tid != GetCurrentThreadId()) {HANDLE _handle =OpenThread(THREAD_ALL_ACCESS,FALSE,GetCurrentThreadId());WaitForSingleObject(_handle,INFINITE);CloseHandle(_handle);//todo: 當(dāng)線程執(zhí)行完后,清理。cleanup(nullptr);} else {thread.state = Death;}}}automan_jvm::lock().unlock();GC::cancel_gc_thread();automan_jvm::end();exit(exitcode); }可以看到,它實(shí)際上是等待當(dāng)前jvm中,所有的線程執(zhí)行完畢,回收相關(guān)的資源。 然后回收gc線程,調(diào)用jvm的end方法收尾,最后退出整個(gè)程序。
因此,我們可以說(shuō): jvm啟動(dòng)之初,掛載了一個(gè)信號(hào)處理程序,該程序負(fù)責(zé)所有的收尾工作,一旦接收到特定信號(hào),整個(gè)程序完成,然后退出。 但是這里我一點(diǎn)不懂,它設(shè)計(jì)成了 while(true)的方式,但是實(shí)際上該代碼塊只能被執(zhí)行一次,這個(gè)問(wèn)題留待以后有緣再回答吧。
ok,如今我可以回退退退到: jvm的run方法那里,繼續(xù)解讀。
信號(hào)處理程序注冊(cè)完后,之后的代碼功能依次是:
1.將傳入的參數(shù)保存到j(luò)vm中;
2.在jvm的線程表中,插入了一個(gè)方法和參數(shù)均為空的線程,注意了,該線程將被稱(chēng)為初始化線程(init_thread),并且該線程不受 jvm的管控,它是本地線程象征性的放入 jvm的線程表。
3.初始化本地方法,實(shí)際上就是將本地的庫(kù)地址進(jìn)行緩存,本質(zhì)上將本地的方法緩存起來(lái),緩存的內(nèi)容包括native包下的所有類(lèi)的核心方法。
4.開(kāi)啟一個(gè)gc線程,同時(shí)該gc線程并不會(huì)放入 jvm的線程表中,而是單獨(dú)的存儲(chǔ)在jvm中。也就是jvm可以直接操縱該線程。 此外,這個(gè)gc線程本身也是一個(gè)真正意義上的線程,它才生成之后,將會(huì)根據(jù)信號(hào),阻塞式的進(jìn)行垃圾清理。它與上面注冊(cè)的那個(gè)信號(hào)處理程序有些不同,我們可以看到其源碼:
unsigned *GC::gc_thread(void *) {// init `cond` and `mutex` first:gc_cond = CreateEvent(NULL,FALSE,FALSE,NULL);gc_cond_mutes=CreateMutex(NULL,FALSE,NULL);while (true) {WaitForSingleObject(gc_cond_mutes,INFINITE);//todo: 這里會(huì)等待gc條件,該線程具有跟進(jìn)程一樣長(zhǎng)的生命周期WaitForSingleObject(gc_cond,INFINITE);ReleaseMutex(gc_cond_mutes);detect_ready();system_gc();} }它會(huì)阻塞式的接收處理信號(hào),每次任務(wù),將會(huì)首先處理線程回收,然后處理資源回收,也就是system_gc()的作用,考慮到這里主線不是討論gc,所以暫時(shí)先不看gc的細(xì)節(jié)。
5.初始化線程調(diào)用launch()操作,進(jìn)行java程序的啟用,需要注意,當(dāng)前的初始化線程(init_thread),也就是根線程。
launch()方法:
該方法可以說(shuō)是關(guān)鍵了,內(nèi)容很細(xì)也很多,考慮到本文的主線任務(wù),將略寫(xiě)本方法,提一提它的功能作用即可,其代碼如下:
從代碼中可以看出,它首先將 init_thread與jvm進(jìn)行了綁定,然后獲取jvm的初始化狀態(tài)。當(dāng)然了首次運(yùn)行時(shí),此時(shí)其肯定未被初始化。 之后它將開(kāi)啟另一個(gè)線程,注意注意了,這是繼 gc線程后,本程序開(kāi)啟的第二個(gè)線程。 這個(gè)線程實(shí)際上將是我們?cè)趈ava端調(diào)用mian方法的那個(gè)線程。同時(shí),它也會(huì)做很多的工作,在本文中,我先暫不討論,后文會(huì)專(zhuān)門(mén)的討論。
之后,init_thread將會(huì)阻塞在此,直到j(luò)ava的mian線程結(jié)束。 之后init_thread會(huì)做如下工作:
1.喚醒所有阻塞的線程。 其代碼如下:
void GC::signal_all_patch() {while(true) {gc_lock().lock();if (!GC::gc()) { // if not in gc, signal all thread is okay.signal_all_thread();break;}gc_lock().unlock();}gc_lock().unlock(); void signal_all_thread() {int size = automan_jvm::thread_num();for (int i = 0; i < size; ++i) {SetEvent(_all_thread_wait_cond);//todo: 這里通過(guò)釋放 CPU 達(dá)到broadst的目的Sleep(0);} }2.判斷當(dāng)前jvm的線程數(shù)量,當(dāng)線程數(shù)量為0的時(shí)候,退出循環(huán)。
3.取消gc線程,以及 調(diào)用 jvm的end進(jìn)行收尾。 整個(gè)程序代碼執(zhí)行完畢,退出。 這里我需要提一下,windows中,主線程退出,則子線程也會(huì)立即退出,無(wú)論子線程是否執(zhí)行完畢(linux則不會(huì))。 所以,這里面對(duì)jvm 的穩(wěn)健性有要求,如果jvm錯(cuò)誤的判斷當(dāng)前系統(tǒng)中的線程數(shù),則會(huì)造成一個(gè)不可預(yù)見(jiàn)的后果。 (注意,當(dāng)java的主線程執(zhí)行完畢后,jvm實(shí)際上進(jìn)入了一個(gè)預(yù)退出狀態(tài),此時(shí)對(duì)線程的管理是十分活躍的,正如 信號(hào)處理程序中的那樣!我不知道這是整個(gè)demo本身的原因,還是說(shuō)發(fā)行版的jvm就是這樣設(shè)計(jì)的。)
總結(jié):
目前,我們知道,整個(gè)jvm的原生階段(不考慮java中新開(kāi)線程的影響),包含了三個(gè)線程,即初始化線程,gc線程,java的主線程。
程序的正常退出包括兩個(gè)途徑:
1.java的mian線程執(zhí)行完畢后,且jvm的其它線程均執(zhí)行完畢,則整個(gè)程序會(huì)因?yàn)榇a執(zhí)行完畢而退出。
2.觸發(fā)了退出的信號(hào),我查了下信號(hào)量: SIGINT, 它好像是 “通過(guò)ctrl+c對(duì)當(dāng)前進(jìn)程發(fā)送結(jié)束信號(hào)”,這就說(shuō)得通了。
在代碼中, 我找到有主動(dòng)發(fā)出這個(gè)信號(hào)的地方,位于runtime/thread中,但是pthread中,是給單個(gè)線程發(fā)送信號(hào),在windows中,我暫未找到相關(guān)的api,因此就是粗略處理的: 其代碼如下:
void ThreadTable::kill_all_except_main_thread(DWORD main_tid) {for (auto iter : get_thread_table()) {if (iter.first == main_tid) continue;else {//這里相當(dāng)于觸發(fā)gcDWORD ret = raise(SIGINT);if (ret!=0) {assert(false);}}} }從它的方法名稱(chēng),以及實(shí)現(xiàn)來(lái)看,它應(yīng)該是要 關(guān)閉除了當(dāng)前線程之外的其它所有 jvm線程。可是一旦觸發(fā)信號(hào)后,實(shí)際上會(huì)導(dǎo)致程序整體退出。 我想這可能是 跟 main_exception_thread有關(guān),那個(gè)方法會(huì)根據(jù)當(dāng)前的線程,而定點(diǎn)關(guān)閉線程。 同時(shí)整個(gè)代碼塊是線程安全的。 這樣就是說(shuō),當(dāng)SIGINT信號(hào)走到了 取消gc線程的時(shí)候,那么所有的線程一定是關(guān)閉了的。 bingo!!
目前尚未驗(yàn)證,但是我想應(yīng)該是這樣的。 只是不知道這里并非根據(jù)線程去觸發(fā) 信號(hào),應(yīng)該是需要進(jìn)一步完善的。
目前為止,整個(gè)jvm的行為算是粗略的分析完了
jvm的啟動(dòng)細(xì)節(jié)1—launch
節(jié)上文,jvm的launch方法的內(nèi)容詳細(xì)講述一下。 在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: 阻塞執(zhí)行 tid線程,tid執(zhí)行完后才往后執(zhí)行WaitForSingleObject(cur_handle,INFINITE); ....其中第一行代碼開(kāi)辟的這個(gè)線程,便是java中的mian線程,也就是java中的主線程。 它接收命令的參數(shù)去執(zhí)行任務(wù)。 其中,scapegoat方法 的代碼如下:
//線程的任務(wù) 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; };通過(guò)代碼不難察覺(jué),它首先將當(dāng)前的線程加入了jvm的線程表中,進(jìn)行管理(注意,此時(shí)jvm線程表中,實(shí)際上管理的線程有兩個(gè)了,一個(gè)是init_thread,它是本地線程的抽象,另一個(gè)就是 mian線程,也就是當(dāng)前加入的這個(gè)線程)。 接著,當(dāng)前線程(mian線程) 將會(huì)調(diào)用 start()方法,將命令行的參數(shù)一并傳遞。 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: 這里是執(zhí)行 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); // checkassert(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); }它會(huì)根據(jù)jvm是否初始化而判定當(dāng)前線程的start是去引導(dǎo)和啟動(dòng)main方法,還是一般的線程。 注意我們?cè)趯W(xué)習(xí)java的時(shí)候,實(shí)例化線程我們需要重寫(xiě)run(){}方法,這個(gè)run方法里面寫(xiě)的實(shí)際上是線程的任務(wù),而線程的啟動(dòng),是由一個(gè)start0本地方法,即jvm調(diào)用的。 調(diào)用之后,會(huì)來(lái)到這里這個(gè)代碼塊。 它將走else下面這個(gè)代碼邏輯,里面實(shí)際上就是執(zhí)行了run方法。 run方法執(zhí)行完后,會(huì)將當(dāng)前的線程狀態(tài)改為 death. 之后這個(gè)線程便會(huì)在特定的時(shí)期被gc給回收掉。
扯遠(yuǎn)了,我們還是看看main線程的操作吧。 它將會(huì)調(diào)用 vm_thread::init_and_do_main()方法。 這個(gè)方法就比較長(zhǎng),我將分塊展示。
1.初始化Class,用于類(lèi)的映射
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首先,調(diào)用java_lang_class::init()方法,它的作用是將 使用標(biāo)識(shí)符與 (核心)類(lèi)進(jìn)行隱射,代碼如下:
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 statestate() = Inited; }然后,使用BootstrapClassLoader去加載 Class類(lèi),注意注意,我們看下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;} // singletonKlass *loadClass(const wstring & classname, ByteStream * = nullptr, MirrorOop * = nullptr,bool = false, InstanceKlass * = nullptr, ObjArrayOop * = nullptr) override;void print() override;void cleanup() override; };從接口中,我們能夠判斷兩條消息:
get_bootstrap調(diào)用會(huì)得到一個(gè)單例對(duì)象;
BootstrapClassLoader有一個(gè)成員變量JarLister,首次調(diào)用時(shí),會(huì)觸發(fā)它的構(gòu)造方法,我們?nèi)タ纯此臉?gòu)造方法:
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\\"); #endifrtjar_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 命令是否執(zhí)行成功的判定]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; // 這里有一個(gè)細(xì)節(jié)。因?yàn)樽詈笠恍袃H僅有個(gè)回車(chē),所以會(huì)讀入空,也就是 s 還是原來(lái)的 s,即最后一個(gè)名字被讀入了兩遍。使用其他的方法對(duì)效率不好,因此在 add_file 中解決了。如果檢測(cè)到有,忽略。if (!Filter::filt(utf8_to_wstring(s))) {this->rjd.add_file(StringSplitter(utf8_to_wstring(s)));}} }通過(guò)代碼可以分析,它做了這樣的事情:
或許到當(dāng)前環(huán)境的 rt.jar,這個(gè)文件時(shí)jvm的核心jar包,我配置的是我本機(jī)環(huán)境的rt.jar,它的版本號(hào)是: jdk_1.8_161。同時(shí),因?yàn)樵瓉?lái)這里使用了配置的方式,但是需要以來(lái)boost,我就給替換了,直接手寫(xiě)死的。
調(diào)用 getjartlist方法,該方法馬上詳述。
調(diào)用rjd的add_file方法。 也將會(huì)詳述。
下面是getjarlist方法:
它做了這樣的事情:
1.通過(guò)jar tf 將rt.jar保存的所有類(lèi) 保存至某一特定文件中;
2.將rt.jar解壓至某一個(gè)特定文件夾中;(注意,這個(gè)文件夾將會(huì)作為是否解壓的標(biāo)準(zhǔn),在同一進(jìn)程中,最多只可能被加壓一次。 我當(dāng)前版本加壓出來(lái)有 2999個(gè)類(lèi),如果解壓信息輸出到控制臺(tái)的話,還是要費(fèi)點(diǎn)時(shí)間。但是我想在發(fā)行版中,一個(gè)環(huán)境下的jvm,應(yīng)該只被解壓一次)。
接著是rtjarDirectory的addfile()方法:
void RtJarDirectory::add_file(StringSplitter && ss) {if (ss.counter() == 0) { // 僅僅在第一次的時(shí)候做檢查,看文件到底存不存在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));}} }它會(huì)將 保存的所有的類(lèi),通過(guò)根據(jù)全限定名稱(chēng)(包名+類(lèi)名)的形式,進(jìn)行分割,最終將 類(lèi)名(去除了包名)的名稱(chēng) 保存進(jìn)一個(gè)智能指針。 這個(gè)結(jié)構(gòu)頗為復(fù)雜,我沒(méi)太看懂,它的定義是這樣的:
shared_ptr<set<shared_ptr<RtJarDirectory>,shared_RtJarDirectory_compare>> subdir; //sub directory我暫且先理解為它保存了所有的類(lèi)名稱(chēng)在內(nèi)存中吧,注意到我獲取的類(lèi)的條目有一萬(wàn)多:
BootstrapClassLoader初始化完成后,就該去loadClass了,以加載Class為例: // add lock simplyLockGuard 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 loadedreturn 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 DEBUGsync_wcout{} << "===----------------- begin parsing (" << target << ") 's ClassFile in BootstrapClassLoader..." << std::endl; #endifClassFile *cf = new ClassFile;ClassFile_Pool::put(cf);f >> *cf; #ifdef DEBUGsync_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_DEBUGBootStrapClassLoader::get_bootstrap().print();MyClassLoader::get_loader().print(); #endifreturn newklass;}//todo:equals to starts with} .........上面為loadClass代碼片段,可以看到第二行,它首先判斷了java.lang.Object.class,通過(guò)前面的那個(gè)智能指針,顯然Object是在里面的。(但是,此時(shí)Object并未加載。)這也印證了我前天文章中所說(shuō)的,Object的唯一性是必須要首先保證的。 同時(shí),加載成功時(shí),會(huì)首先 實(shí)例一個(gè)ClassFile放入 類(lèi)池,這個(gè)類(lèi)對(duì)象保存的是 字節(jié)碼二進(jìn)制流!!! 接著,實(shí)例化一個(gè) 實(shí)例類(lèi)對(duì)象,將該類(lèi)對(duì)象保存進(jìn)入 system_classmap。
3.設(shè)置Object,以及基本類(lèi)型的單例映像:
該步驟是通過(guò) Class完成的。
該方法將 8個(gè)基本類(lèi)型,和void 以Mirror實(shí)例的形式放入了一個(gè)緩存。
...... 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.classauto 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 的來(lái)源,可能要修改init_thread->set_field_value(THREAD L":eetop:J", new LongOop((uint64_t)GetCurrentThreadId()));//todo: 這里的線程優(yōu)先級(jí)還沒(méi)有綁定到 thread句柄上, 通過(guò) setThreadPriorityinit_thread->set_field_value(THREAD L":priority:I", new IntOop(5));//todo: 這里通過(guò) openthread 根據(jù)當(dāng)前線程的id 獲取到線程句柄 注意,當(dāng)前線程的句柄不能關(guān)閉ThreadTable::add_a_thread(GetCurrentThreadId(), init_thread, this);加載完Thread之后,會(huì)生成一個(gè)Thread的實(shí)例對(duì)象。之后會(huì)進(jìn)行類(lèi)的初始化,注意這是jvm中第一次類(lèi)的初始化,它會(huì)一直向上初始化直到Object。
設(shè)置相關(guān)屬性后,放入jvm的線程表中。 注意了,此時(shí)有在線程表中,就有了兩個(gè)個(gè)線程了。 但是注意這個(gè)線程跟第二個(gè)線程的id號(hào)是一致的,同時(shí),它并不是真正意義上的線程,只是一個(gè)抽象。 在線程表的插入中有如下代碼:
if (get_thread_table().insert(make_pair(tid, make_tuple(get_thread_table().size(), _thread, t))).second == false) { // 如果原先已經(jīng)插入了的話,那么就復(fù)用原先的 thread_no.number = std::get<0>(get_thread_table()[tid]);}也就是說(shuō),the_whole_world的線程表中的線程數(shù)仍然是兩個(gè)。
5.加載并實(shí)例化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));注意,這里除了 類(lèi)初始化之外,還初始化了一個(gè) ThreadGroup對(duì)象。 實(shí)例化的threadGroup為主線程組。
6.加載并初始化System:
// 3. load System classauto 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用于設(shè)置相關(guān)的屬性,并且完成 當(dāng)前程序的標(biāo)準(zhǔn)輸入流,輸出流,錯(cuò)誤流的綁定。 windows上,每個(gè)程序的標(biāo)準(zhǔn)輸入輸出和錯(cuò)誤流就是控制臺(tái)。
7.加載并實(shí)例化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作用暫時(shí)不清楚,LauncherHelper會(huì)用于引導(dǎo) Launcher然后使用 AppClassLoader去加載用戶類(lèi)。
8.加載動(dòng)態(tài)調(diào)用的類(lèi):
// 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.執(zhí)行main:
launch流程結(jié)束。
jvm的啟動(dòng)細(xì)節(jié)2—classloader
說(shuō)到這個(gè)古老的話題,那可得追溯到我寫(xiě)文章開(kāi)始。 那一年春節(jié),我決定看看源碼,一上來(lái)就猛的 想把那個(gè)雙親加載機(jī)制給搞明白。 事實(shí)上,一直沒(méi)怎么搞明白,唯一的作用是給了自己學(xué)習(xí)的動(dòng)力。 但是今天我想借著這個(gè)機(jī)會(huì),是可以完全搞懂的。
上午說(shuō)到,jvm在加載客戶類(lèi)之前,會(huì)啟動(dòng)一個(gè)LauncherHelper類(lèi),代碼如下:
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: 到這里應(yīng)該是java層面的類(lèi)加載器開(kāi)始生效!!! MirrorOop *main_class_mirror = (MirrorOop *)this->execute(); 接著去看看 checkAndLoadMain方法:之后通過(guò)ClassLoader的loadClass方法,通過(guò)查找虛擬表,調(diào)用Launcher的AppClassLoader的loadClass方法,再調(diào)用之前,將會(huì)首先進(jìn)行AppClassClassloader的初始化(這個(gè)初始化過(guò)程蠻復(fù)雜的):
花了較多的精力去看這塊的源碼,主要原因基于以下幾點(diǎn):
1.windows中由于文件系統(tǒng)路徑分隔符的原因,我在調(diào)試時(shí)就遇到了一個(gè)坑,即我明明需要使用 文件系統(tǒng)去加載,但是它卻使用了JarLoader去加載。 現(xiàn)在回過(guò)頭來(lái),知道了其原因: 首先在LauncherHelper中會(huì)根據(jù)模式選擇加載器,其次會(huì)判斷java.class.path下面的所有配置路徑,如果為文件夾,則會(huì)匹配為 fileLoader,如果為文件,則會(huì)使用默認(rèn)的loader,而實(shí)際上默認(rèn)的loader,其最終采用的還是Jarloader的方式加載的。(我的問(wèn)題就出在這!)。
2.AppClassLoader與ExtClassLoader都繼承自URLClasspath,因此必須弄明白URLClasspath是在什么時(shí)機(jī)初始化的,以及相應(yīng)的參數(shù)都是什么。 關(guān)于URLClasspath的作用,網(wǎng)上帖子挺多。 它的幾個(gè)關(guān)鍵方法: loadClass,findClass,defineClass 可以用于驗(yàn)證雙親委派機(jī)制的運(yùn)行流程。
我在調(diào)試時(shí),分別給loadClass 和 defineClass添加了錨點(diǎn), 最終實(shí)際上是可以印證雙親委派模型的。 由于當(dāng)時(shí)未截圖,因此這里就不再繼續(xù)操作了。(因?yàn)檫@個(gè)過(guò)程實(shí)在是有些痛苦~,感興趣的小伙伴可以親自調(diào)試一下)。
另外強(qiáng)調(diào)一點(diǎn)就是,以前沒(méi)有認(rèn)識(shí)到URLClassLoader在java中的重要性,以及URLClassLoader與URLClasspath的關(guān)系,這是一個(gè)十分有趣的問(wèn)題。
此外,在AppclassLoader加載類(lèi)的過(guò)程中,它的流將會(huì)以匿名內(nèi)部類(lèi)的形式給出:
就到這吧,原計(jì)劃寫(xiě)的很詳細(xì)的,但是真寫(xiě)了太細(xì)了吧,速度太慢,自己又是個(gè)急性子。 后續(xù)再補(bǔ)充細(xì)節(jié),還需要進(jìn)一步的學(xué)習(xí)補(bǔ)充。。。
總結(jié)
- 上一篇: 如何打开USB OTG功能:
- 下一篇: 微信如何做好服务器,如何用免费服务器做微