摘要:拆解虛擬機(jī)的基本步聚如下首先,要等待到自身成為唯一一個正在運(yùn)行的非守護(hù)線程時,在整個等待過程中,虛擬機(jī)仍舊是可工作的。將相應(yīng)的事件發(fā)送給,禁用,并終止信號線程。
本文簡單介紹HotSpot虛擬機(jī)運(yùn)行時子系統(tǒng),內(nèi)容來自不同的版本,因此可能會與最新版本之間(當(dāng)前為JDK12)存在一些誤差。
1.命令行參數(shù)處理
HotSpot虛擬機(jī)中有大量的可影響性能的命令行屬性,可根據(jù)他們的消費(fèi)者進(jìn)行簡單分類:執(zhí)行器消費(fèi)(如-server -client選項(xiàng)),執(zhí)行器處理并傳遞給JVM,直接由JVM消費(fèi)(大多)。
這些選項(xiàng)可分為三個主要的類別:標(biāo)準(zhǔn)選項(xiàng),非標(biāo)準(zhǔn)選項(xiàng),開發(fā)者選項(xiàng)。標(biāo)準(zhǔn)選項(xiàng)是指所有的JVM不同實(shí)現(xiàn)均可以處理且在不同版本之間穩(wěn)定可用的選項(xiàng)(但是也可以deprecated)。
以-X開頭的選項(xiàng)是非標(biāo)準(zhǔn)選項(xiàng)(不保證所有JVM虛擬機(jī)的實(shí)現(xiàn)均支持),后續(xù)的JAVA SDK更新也不保證會對它進(jìn)行通知。使用-XX開頭的為開發(fā)者選項(xiàng),它一般需要特定的系統(tǒng)環(huán)境(以保證實(shí)現(xiàn)正確的操作)和足量的權(quán)限(以訪問系統(tǒng)配置參數(shù)),這些實(shí)現(xiàn)應(yīng)當(dāng)慎重使用,相應(yīng)的選項(xiàng)在更新后也并不保證通知到用戶。
命令行參數(shù)控制了JVM內(nèi)部變量的屬性值,這些參數(shù)同時具備"類型"與"值"。對于布爾型的屬性值,以+或-置于參數(shù)之前,可分別表示該屬性的值為true或false。對于需要其他數(shù)據(jù)的變量,同樣有許多機(jī)制可以設(shè)置其值數(shù)據(jù)(很遺憾并不統(tǒng)一),一部分參數(shù)在格式上要求在屬性名稱后直接跟隨屬性值,有些不需要分隔符,有些又不得不加上分隔符,分隔符又可能是":","="等,如-XX:+OptionName, -XX:-OptionName, and -XX:OptionName=。
大多整型的參數(shù)(如內(nèi)存大小)可接受"k","m","g"分別代表kb,mb,gb這種簡寫形式。
2.虛擬機(jī)運(yùn)行周期
執(zhí)行器
HotSpot虛擬機(jī)有幾種Java標(biāo)準(zhǔn)版的執(zhí)行器,在unix系統(tǒng)上即java命令,在windows系統(tǒng)下即java和javaw(javaw其實(shí)是指基于網(wǎng)絡(luò)的執(zhí)行器)。從屬于虛擬機(jī)啟動過程的執(zhí)行器操作有:
a.解析命令行選項(xiàng),部分選項(xiàng)直接由執(zhí)行器自身消費(fèi),如-client和-sever屬性被用來決斷加載合適的vm庫,其他的屬性則作為虛擬機(jī)初始化參數(shù)(JavaVMInitArgs)傳遞給vm。
b.如果未明確指定選項(xiàng),執(zhí)行器來確定堆的大小和編譯器類型(是client還是server)。
c.確立如LD_LIBRARY_PATH 和 CLASSPATH等環(huán)境變量。
d.如果未在命令行中明確指定主類,執(zhí)行器會從jar文件清單中找出主類名稱。
e.執(zhí)行器會在一個新創(chuàng)建的線程(非原生線程)中使用JNI_CreateJavaVM來創(chuàng)建虛擬機(jī)實(shí)例。 注意,在原生線程中創(chuàng)建vm會極大的減少定制vm的可能性,如windows中的棧大小等。
f.一旦vm創(chuàng)建并初始化成功,加載主類成功,執(zhí)行器可從主類中得到main方法的屬性,然后使用CallStaticVoidMethod執(zhí)行主方法并以命令行參數(shù)為它的方法入?yún)ⅰ?br> g.當(dāng)java主方法執(zhí)行完成時,檢查和清理任何可能已發(fā)生的掛起的異常,返回退出狀態(tài)。它會使用ExceptionOccurred來清理異常,方法如果執(zhí)行成功,它會給調(diào)用進(jìn)程返回一個0值,否則為其他值。
h.使用DetachCurrentThread解除主線程的關(guān)聯(lián),這樣減少了線程的數(shù)量,保證可安全調(diào)用DestroyJavaVM,它也能保證線程不在vm中執(zhí)行操作,棧中不再有存活的棧楨。
最重要的兩個階段是JNI_CreateJavaVM以及DestroyJavaVM,下面詳述。
JNI_CreateJavaVM執(zhí)行步驟:
首先保證沒有兩個線程同時調(diào)用此方法,從而保證不在同一進(jìn)程中出現(xiàn)兩個vm實(shí)例。當(dāng)在一個進(jìn)程空間中達(dá)到一個初始化點(diǎn)時,該進(jìn)程空間中不能再創(chuàng)建vm,該點(diǎn)也被稱為“不返回的點(diǎn)”(point of no return)。原因在于此時vm已創(chuàng)建的靜態(tài)數(shù)據(jù)結(jié)構(gòu)不能夠重新初始化。
接下來,要對JNI的版本支持進(jìn)行檢查,檢測gc日志的ostream是否初始化。此時會初始化一些操作系統(tǒng)模塊,如隨機(jī)數(shù)生成器,當(dāng)前進(jìn)程號,高分辨率的時間,內(nèi)存頁大小和保護(hù)頁等。
解析傳入的參數(shù)和屬性值,并存放留用。初始化java標(biāo)準(zhǔn)系統(tǒng)屬性。
基于上一步解析的參數(shù)和屬性進(jìn)一步創(chuàng)建和初始化系統(tǒng)模塊,這一次的初始化是為同步,棧內(nèi)存,安全點(diǎn)頁做準(zhǔn)備。在此時如libzip,libhpi,libjava,libthread等庫也完成了加載,同時完成信號句柄的初始化和設(shè)定,并初始化線程庫。
接下來初始化輸出流日志,任何必需的代理庫(hprof jdi等)均于此時完成初始化和開啟。
完成線程狀態(tài)的初始化以及持有了線程操作所需的指定的數(shù)據(jù)的線程本地存儲(TLS Thread Local Storage)的初始化。
全局?jǐn)?shù)據(jù)的初始化,如事件日志,操作系統(tǒng)同步,性能內(nèi)存(perfMemory),內(nèi)存分配器(chunkPool)等。
到此時開始創(chuàng)建線程,會創(chuàng)建java版本的主線程并綁定到一個當(dāng)前操作系統(tǒng)線程上。然而這個線程還不能被Threads線程列表感知,完成java級別的線程初始化和啟用。
緊接著進(jìn)行余下部分的全局模塊的初始化,它們包括啟動類加載器(BootClassLoader),代碼緩存(CodeCache),解釋器(Interpreter),編譯器(Compiler),JNI,系統(tǒng)字典(SystemDictionary),Universe。此時便已到達(dá)前述的“不返回的點(diǎn)”,也就是說,我們此時已不能在進(jìn)程的地址空間中再創(chuàng)建一個vm實(shí)例了。
主線程會在此時被加入到線程列表中,這一步首先要對Thread_Lock進(jìn)行加鎖操作。此處會對Universe(戲稱為小宇宙,即所需的全局?jǐn)?shù)據(jù)結(jié)構(gòu))進(jìn)行健全檢查。此時創(chuàng)建執(zhí)行所有重要vm函數(shù)的VMThread,創(chuàng)建完成后,即達(dá)到一個合適的點(diǎn),在這個點(diǎn),可發(fā)出適當(dāng)?shù)腏VMTI事件通知當(dāng)前jvm的狀態(tài)。
加載并初始化一些類,包含java.lang.String,java.lang.System,java.lang.Thread,java.lang.ThreadGroup,java.lang.reflect.Method,java.lang.ref.Finalizer,java.lang.Class,以及系統(tǒng)類中的其他成員。在這一刻,vm已經(jīng)完成初始化并且可操作,但是并未具備完整的功能。
到這一步,信號處理器線程也被開啟,同時也完成了啟動編譯器線程和CompileBroker線程,以及StatSampler和WatcherThreads等輔助線程,此時vm具備了完整的功能,生成JNIEnv信息并返回給調(diào)用者,此時的vm已經(jīng)準(zhǔn)備就緒,可服務(wù)新的JNI請求。
DestroyJavaVM執(zhí)行步驟
DestroyJavaVM的調(diào)用有兩種情況:執(zhí)行器調(diào)用它拆解vm,或vm自身在出現(xiàn)嚴(yán)重錯誤時調(diào)用。拆解虛擬機(jī)的基本步聚如下:
首先,要等待到自身成為唯一一個正在運(yùn)行的非守護(hù)線程時,在整個等待過程中,虛擬機(jī)仍舊是可工作的。
調(diào)用java.lang.Shutdown.shutdown()方法,它會執(zhí)行java級別的關(guān)閉勾子方法,如果有退出終結(jié)器可用,運(yùn)行相應(yīng)的終結(jié)器(finalizer)。
調(diào)用before_exit()為vm退出做出準(zhǔn)備,運(yùn)行vm級別的關(guān)閉勾子(它們是用JVM_OnExit()注冊的),停止剖析器(Profiler),采樣器(StatSampler),Watcher和GC線程。將相應(yīng)的事件發(fā)送給JVMTI/PI,禁用JVMPI,并終止信號線程。
調(diào)用JavaThread的exit方法,釋放JNI句柄塊,移除棧保護(hù)頁,把此線程從線程列表中移除,從這個點(diǎn)起,任何java代碼不可被執(zhí)行。
終止vm線程,它會把當(dāng)前的vm帶到安全點(diǎn)并終止編譯器線程。在安全點(diǎn),應(yīng)注意任何可能會在安全點(diǎn)阻塞的功能都不可使用。
禁用JNI/JVM/JVMPI屏障的追蹤。
給native代碼中依舊在運(yùn)行的線程設(shè)置_vm_existed標(biāo)記。
刪除這個線程。
調(diào)用exit_globals刪除IO和PerfMemory資源。
返回調(diào)用者。
3.虛擬機(jī)類加載(class loading)
虛擬機(jī)要負(fù)責(zé)常量池符號的解析,它需要對有關(guān)的類和接口先后進(jìn)行裝載(loading),鏈接(linking)然后初始化。一般用“類加載機(jī)制”來描述把一個類或接口的名稱映射到一個class對象的過程,相應(yīng)的,JVMS定義了詳細(xì)的裝載,鏈接和初始化階段的協(xié)議。
類的加載是在字節(jié)碼解析過程中完成的,典型是當(dāng)一個類文件中的常量池符號需要被解析時。有一些JAVA的api會觸發(fā)這個過程,如Class.forName(),classLoader.loadClass(),反射api,以及JNI_FindClass均能初始化類的加載。虛擬機(jī)自身也能初始化類加載。虛擬機(jī)會在啟動時加載如Object,Thread等核心類。裝載一個類需要裝載所有的超類和超接口。且對于鏈接階段的類文件驗(yàn)證過程,可能需要裝載額外的類。
虛擬機(jī)和JAVA SE類加載庫共同承擔(dān)了類的加載,虛擬機(jī)執(zhí)行了常量池的解析,類和接口的鏈接和初始化。加載階段是vm和特定的類加載器(java.lang.ClassLoader)之間的一個協(xié)作過程。
類加載階段
裝載階段,根據(jù)類或接口的名稱,在類文件中找出二進(jìn)制語義,定義類并創(chuàng)建java.lang.Class對象。如果在類文件中找不到二進(jìn)制表示,則拋出NoClassDefFound錯誤。此外裝載階段也做了一些類文件的在語法上的格式檢查,檢查不通過會拋出ClassFormatError或UnsupportedClassVersionError。在完成裝載之前,vm必須載入所有的超類和超接口。如果類繼承樹存在問題,如類直接或間接地自己繼承或?qū)崿F(xiàn)自己,則vm會拋出ClassCircularityError。若vm發(fā)現(xiàn)類的直接接口不是接口,或者直接父類是一個接口,則會拋出IncompatibleClassChangeError。
類加載的鏈接階段首先做一些校驗(yàn),它會檢測類文件的語義,常量池符號以及類型檢測,這個過程可能會拋出VerifyError。鏈接階段接下來進(jìn)行一些準(zhǔn)備工作,它會為靜態(tài)字段進(jìn)行創(chuàng)建和初始化標(biāo)準(zhǔn)默認(rèn)值,并分配方法表。注意,到此步為止不會進(jìn)行任何java代碼的執(zhí)行。之后鏈接階段還有一個可選的步驟,即符號引用的解析。
接下來是類的初始化階段,它會運(yùn)行類的靜態(tài)初始化器,初始化類的靜態(tài)字段。這是類的java代碼的第一次執(zhí)行。注意類的初始化需要超類的初始化,但不包含超接口的初始化。
JAVA虛擬機(jī)規(guī)范(JVMS)規(guī)定了類的初始化發(fā)生在類的第一次“活化使用”,java語言規(guī)范(JLS)允許鏈接階段的符號解析過程在不破壞java語義前提下的靈活性,裝載,鏈接和初始化的每一個步驟都要在前一步驟完成后進(jìn)行。為了性能考慮,HotSpot虛擬機(jī)一般會等到要去初始化一個類時才會去進(jìn)行類的裝載和鏈接。所以舉個簡單的例子,如果類A引用了類B,那么加載類A將不會必然導(dǎo)致B的加載,除非在驗(yàn)證階段必需。當(dāng)執(zhí)行了第一個引用B的指令時,將會導(dǎo)致B的初始化,而這又需要先對類B進(jìn)行裝載和鏈接。
類加載的委托機(jī)制
當(dāng)一個加載器被要求查找和加載一個class時,它可以請求另一個類加載器去做實(shí)際的加載工作。這個機(jī)制被稱為加載委托。第一個類加載器是一個“初始化加載器”,而最終定義了該類的類加載器被稱為“定義加載器”,在字節(jié)碼解析的例子中,初始化加載器負(fù)責(zé)該類的常量池符號的解析。
類加載器是分層定義的,每個類加載器可有委托的雙親。委托機(jī)制定義了二進(jìn)制類表示的檢索順序。JAVASE類加載器按層序檢索啟動類加載器,擴(kuò)展類加載器和系統(tǒng)類加載器。系統(tǒng)類加載器同時也是默認(rèn)的應(yīng)用類加載器,它會運(yùn)行main方法并從類路徑下加載類。應(yīng)用類加載器可以是JAVASE 類加載器庫中的實(shí)現(xiàn),也可以由應(yīng)用開發(fā)人員實(shí)現(xiàn)。JAVASE類庫實(shí)現(xiàn)了擴(kuò)展類加載器,它負(fù)責(zé)加載jre下lib/ext目錄中的類。
作者在“54個JAVA官方文檔術(shù)語”一文中曾說過,這一機(jī)制已經(jīng)不適用于JAVA9以上版本的描述,如果去查詢有關(guān)文章,可以發(fā)現(xiàn)這個經(jīng)典的類加載委托機(jī)制其實(shí)已經(jīng)歷過三次破壞(委托機(jī)制出廠時晚于加載器本身,破壞一;線程上下文類加載器,破壞二;熱部署的后門,破壞三),而作者個人認(rèn)為類加載器支持JAVA9之后的模塊路徑的加載也是一種破壞,它們之間不再是簡單的委托加載,也不僅從類路徑下加載,不同路徑加載到的模塊也有不同的處理機(jī)制,詳細(xì)描述見該文。
啟動類加載器是由vm實(shí)現(xiàn)的,它從BOOTPATH下加載類,包含rt.jar中的類定義。為了快速啟動,vm也會通過類數(shù)據(jù)共享(cds)來處來類的預(yù)加載。關(guān)于cds,在最新的幾版jdk中有所更新,我們在稍后的章節(jié)中簡述。
類型安全
類或者接口名是由包含包名稱的全限定名定義的。一個類的類型由該全限定名和類加載器所唯一定義,所以類加載器其實(shí)可以理解為一個名稱空間,兩個不同類加載器定義的同一個類實(shí)際上會是兩個class類型。
vm會對自定義類加載器進(jìn)行限定,保證不能與類型安全發(fā)生沖突。當(dāng)類A中調(diào)用類B的方法時,vm通過追蹤和檢查加載器約束保證兩個類的加載器在方法參數(shù)和返回值上協(xié)商一致。
HotSpot中的類元數(shù)據(jù)
類加載的結(jié)果是在永久代(舊版)創(chuàng)建一個instanceKlass或者arrayKlass。instanceKlass指向一個java.lang.Class的實(shí)例,虛擬機(jī)c++代碼通過klassOop訪問instanceClass。
HotSpot內(nèi)部類加載數(shù)據(jù)
HotSpot虛擬機(jī)為了追蹤類加載而維護(hù)了三張主哈希表。分別是SystemDictionary表,它包含被加載的類,它們映射鍵為一個類名/類加載器對,值為一個klassOop,它同時包含了類名/初始化加載器對和類名/定義加載器對,目前只有在安全點(diǎn)才可以移除它們;PlaceholderTable表,它包含當(dāng)前正在被載器的類,它被用于前述ClassCircularityError檢查和支持多線程類加載的加載器進(jìn)行并行加載;LoaderConstraintTable,它追蹤類型安全檢查約束。這些哈希表都由一個鎖SystemDictionary_lock來保護(hù),一般情況下vm中的類加載階段是使用類加載器對象鎖串行執(zhí)行的。
4.字節(jié)碼驗(yàn)證和格式檢查
JAVA語言是類型安全的,標(biāo)準(zhǔn)的java編譯器會生產(chǎn)可用的類文件和類型安全的代碼,但是jvm不能保證代碼是由可信任的編譯器生成的,因此它必須在鏈接時進(jìn)行字節(jié)碼校驗(yàn)(bytecode verification)重建類型安全。
字節(jié)碼校驗(yàn)的規(guī)范詳見java虛擬機(jī)規(guī)范的4.8節(jié)。規(guī)范中規(guī)定了JVM校驗(yàn)的代碼動態(tài)和靜態(tài)約束。如果發(fā)現(xiàn)了任何與約束沖突的地方,虛擬機(jī)將會拋出VerifyError并阻斷類的鏈接。
可靜態(tài)檢查的字節(jié)碼約束有很多,"ldc"碼(Low Disparity Code 低差別編碼)的操作數(shù)必須為一個可用的常量池索引,它的類型是CONSTANT_Integer, CONSTANT_String 或 CONSTANT_Float。其他指令需要的檢查參數(shù)類型和個數(shù)的約束需要對代碼動態(tài)分析,這樣來決定執(zhí)行時哪個操作數(shù)可出現(xiàn)在表達(dá)式棧。
目前,有兩種辦法(截止1.6)分析字節(jié)碼并決定在每一條指定中出現(xiàn)的操作數(shù)類型和個數(shù)。傳統(tǒng)的辦法被稱作“類型推斷”,它通過對每個字節(jié)碼進(jìn)行抽象解釋,在代碼的分支處或異常句柄處進(jìn)行類型狀態(tài)的合并。整個分析過程會迭代全部的字節(jié)碼,直到發(fā)現(xiàn)這些類型的“穩(wěn)態(tài)”。如果不能達(dá)到穩(wěn)態(tài),或者結(jié)果類型與一些字節(jié)碼的約束沖突,那么拋出VerifyError。這一步的驗(yàn)證代碼位于外部庫libverify.so中,它使用JNI去收集所需的類和類型的信息。
在JDK6中出現(xiàn)了第二種被稱為“類型驗(yàn)證“的方法,在這種方法中,java編譯器通過代碼屬性,StackMapTable來提供每一個分支和異常目標(biāo)的穩(wěn)態(tài)類型信息。StackMapTable包含大量的棧圖楨,每一個楨表示方法的某一個偏移量的表達(dá)式棧和局部變量表中的條目類型。jvm接下來只需要遍歷字節(jié)碼并驗(yàn)證字其中的類型正確性。這是一個已經(jīng)在JAVAME CLDC中使用的技術(shù)。因?yàn)樗《欤蓑?yàn)證方法vm自身即可構(gòu)建。
對于所有版本號低于50,創(chuàng)建早于JDK6的類文件,jvm會使用傳統(tǒng)的類型推薦方式驗(yàn)證類文件,否則會使用新辦法。
5.類數(shù)據(jù)共享(cds)
類數(shù)據(jù)共享是一個JDK5引入的功能,旨在提高java程序語言應(yīng)用的啟動時間,尤其是小型應(yīng)用,同時,它也能減少內(nèi)存占用。當(dāng)jre安裝在32位系統(tǒng)時并且使用sun提供的安裝器時,安裝器會從系統(tǒng)jar中載入一組類并生成一種內(nèi)部的格式,然后轉(zhuǎn)儲為一個文件,這個文件被稱作”共享存檔“。如果沒有使用sun提供的jre安裝器,也可以手動執(zhí)行。在后續(xù)的jvm執(zhí)行時,這個共享存檔文件被映射進(jìn)內(nèi)存,節(jié)省了其他jvm裝載類和元數(shù)據(jù)的時間。
目前官方對于cds的文檔未整理完善,在截止到JAVA8的有關(guān)文檔中,仍可以見到這樣一句描述:Class data sharing is supported only with the Java HotSpot Client VM, and only with the serial garbage collector,即類共享目前只在HotSpot client虛擬機(jī)中支持,且只能使用serial垃圾收集器。而在JAVA9-12的若干新特性中,也對cds有過一些更新描述,如JAVA10中對類數(shù)據(jù)的共享包含了應(yīng)用程序的類,在JAVA11中模塊路徑也支持了cds。但作者并未在專門的垃圾收集器中找到大篇幅的詳述,不過根據(jù)jdk12的jvm文檔中介紹,cds已經(jīng)在 G1, serial, parallel, 和 parallelOldGC 幾種垃圾收集器中支持,且默認(rèn)使用G1的128M堆內(nèi)存。且G1在JDK7中已出現(xiàn),在JDK9中已經(jīng)成為默認(rèn)的垃圾收集器,parallel 出現(xiàn)的相對更早,因此作者嚴(yán)重懷疑JAVA8中相應(yīng)文檔描述的準(zhǔn)確性,好在我們可以直接去看最新版。
cds可以減少啟動時間,因?yàn)樗鼫p少了裝載固定的類庫的開銷,應(yīng)用程序相對于使用的核心類越小,cds就相當(dāng)節(jié)省了越多的啟動時間。cds同時也有兩種方式減少了jvm實(shí)例的內(nèi)存占用。首先,一部分共享存檔文件被映射進(jìn)內(nèi)存并作為只讀的庫,多個jvm進(jìn)程不需要重復(fù)占用進(jìn)程的內(nèi)存空間;其次,因?yàn)楣蚕泶鏅n文件中包含的類數(shù)據(jù)已經(jīng)是jvm使用的格式,處理rt.jar(低于9的版本)所需的額外內(nèi)存開銷也可以省去了,這使得多個應(yīng)用在同一機(jī)器上能夠更優(yōu)的并發(fā)執(zhí)行。
在HotSpot虛擬機(jī)中,類共享的實(shí)現(xiàn)實(shí)際是在永久代(元空間)中開辟了新的內(nèi)存區(qū)域存放共享數(shù)據(jù)。存檔文件名為”classes.jsa“,它會在vm啟動時映射進(jìn)這個空間。后續(xù)的管理由vm內(nèi)存管理子系統(tǒng)負(fù)責(zé)。
共享數(shù)據(jù)是只讀的,它包含常量方法對象(constMethodOops),符號對象(symbolOops),基本類型數(shù)組,多數(shù)字符數(shù)組。可讀寫的共享數(shù)據(jù)包含可變的方法對象(methodOops),常量池對象(constantPoolOops),vm內(nèi)部的java類和數(shù)組實(shí)現(xiàn)(instanceKlasses和arrayKlasses), 以及大量的String,Class和Exception對象。
作者看來,近幾版的jdk關(guān)于cds的幾處更新明顯借鑒了一些如tomcat等服務(wù)器的機(jī)制,適配越來越多的云生產(chǎn)環(huán)境,減少內(nèi)存開銷和啟動開銷都是為云用戶省錢的方式。
6.解釋器
當(dāng)前HotSpot解釋器是一個基于模板的解釋器,它被用來執(zhí)行字節(jié)碼。HotSpot在啟動時運(yùn)行時用InterpreterGenerator在內(nèi)存中利用TemplateTable(每個字節(jié)碼有關(guān)的匯編代碼)中的信息生成一個解釋器實(shí)例。模板是每個字節(jié)碼的描述,模板表定義了所有模板并提供了獲取指定字節(jié)碼的訪問方法。在jvm啟動時,可使用-XX:+PrintInterpreter打印有關(guān)的模板表信息。
執(zhí)行效果上看,模板好于經(jīng)典的switch語句循環(huán)的方式,原因也很簡單,首先switch語句執(zhí)行重復(fù)的比較操作來得到目標(biāo)字節(jié)碼,最極端情況它可能需要對一個給定的指定比較所有的字節(jié)碼;第二,模板使用共享的棧來傳遞java參數(shù),同時本地c方法棧被vm自身來使用,大量的jvm內(nèi)部變量是用c變量存放的(如線程的程序計數(shù)器或棧指針),它們不保證永久存放在硬件寄存器中,管理這些軟件的解釋結(jié)構(gòu)會消耗總執(zhí)行時間中的相當(dāng)可觀的一部分。
從全局來看,HotSpot解釋器大幅彌合了虛擬機(jī)和實(shí)體機(jī)器之間的裂縫,它大大加快了解釋的時間,但是犧牲了很多代碼的機(jī)器塊,同時也增大了代碼大小和復(fù)雜度,也需要一些代碼的動態(tài)生成。很明顯,debug機(jī)器動態(tài)生成的代碼要比靜態(tài)代碼更加困難。
對于一些對匯編語言來說過于復(fù)雜的操作,如常量池的查找,解釋器會運(yùn)行時調(diào)用vm來完成。
HotSpot解釋器也是整個HotSpot自適應(yīng)優(yōu)化歷史中重要的一部分,自適應(yīng)優(yōu)化解決了JIT編譯的問題,大部分情況下,幾乎所有的程序都是用大量時間執(zhí)行極少量的代碼,因此運(yùn)行時不需要逐方法編譯,vm僅使用解釋器來立即運(yùn)行程序,分析代碼在程序中運(yùn)行的次數(shù),避免編譯不頻繁運(yùn)行的程序代碼(大多數(shù)),這樣HotSpot編譯器可以專注于程序中最需要性能優(yōu)化的部分,并不增加全局的編譯時間,在程序持續(xù)運(yùn)行期間進(jìn)行動態(tài)的監(jiān)控,達(dá)到最適應(yīng)用戶需要的目的。
7.JAVA異常處理
jvm使用異常作為一個信號,它說明程序中出現(xiàn)了與java語言語義相沖突的事件,數(shù)組越界是一個極簡的案例。異常會導(dǎo)致控制流從異常發(fā)生或拋出的點(diǎn)轉(zhuǎn)到程序指定的處理點(diǎn)或捕獲點(diǎn)的一次非本地轉(zhuǎn)換。HotSpot解釋器和動態(tài)編譯器在運(yùn)行時協(xié)作實(shí)現(xiàn)了異常的處理。異常處理有兩種簡單案例,異常拋出并由同一方法捕獲,異常拋出并由調(diào)用者捕獲。后一種情況稍微復(fù)雜一些,因?yàn)樾枰归_棧來找出恰當(dāng)?shù)奶幚碚摺?br> 要初始化一個異常有多個方式,如throw字節(jié)碼,從vm內(nèi)部調(diào)用中返回,JNI調(diào)用中返回,或java調(diào)用中返回,最后一個情況其實(shí)是前三者的后一階段。當(dāng)vm意識到有異常拋出時,執(zhí)行運(yùn)行時系統(tǒng)去找出該異常最近的處理器,這一過程會用到三片信息:當(dāng)前方法,當(dāng)前字節(jié)碼,異常對象。如果當(dāng)前方法沒有找到處理器,如上面提到的,將當(dāng)前活化的棧楨出棧,進(jìn)程將在此前的棧楨中迭代重復(fù)上述步驟。一旦找到了合適的處理器,vm更新執(zhí)行狀態(tài),跳轉(zhuǎn)到相應(yīng)的處理器,java代碼在相應(yīng)位置繼續(xù)執(zhí)行。
8.同步
廣泛來講,可以把“同步”定義為一個阻止或恢復(fù)不恰當(dāng)?shù)牟l(fā)交互(一般稱為競態(tài))的一個機(jī)制。在java中,并發(fā)通過線程來表示,鎖排他是java中常見的一個同步案例,這一過程中,只有一個線程同時被允許訪問一段保護(hù)的代碼或數(shù)據(jù)。
HotSpot提供了java監(jiān)視器的概念,線程可通過監(jiān)視器來排它的運(yùn)行應(yīng)用代碼。監(jiān)視器只有兩個狀態(tài):鎖或者未鎖,一個線程可以在任何時間持有(鎖住)監(jiān)視器。只有在獲取了監(jiān)視器后,線程才能進(jìn)入被監(jiān)視器保護(hù)的代碼塊。在java中這類被監(jiān)視器保護(hù)的代碼塊稱為同步代碼塊。
無競態(tài)的同步包含了大多的同步情況,它由常量時技術(shù)實(shí)現(xiàn)。java對于同步機(jī)制做了大量的優(yōu)化,偏向鎖技術(shù)是其中之一,因?yàn)榇蠖鄶?shù)的對象一生只被最多一個線程持有鎖,因此允許該線程將監(jiān)視器偏向給自己,一旦偏向,該線程后續(xù)鎖和解鎖不再需要額外又昂貴的原子指令開銷。
對于有競態(tài)的同步操作場景,使用高級自適應(yīng)自旋技術(shù)提高吞吐量。即使此時應(yīng)用中有大量的競態(tài),在經(jīng)歷這些優(yōu)化后,同步操作性能已經(jīng)大幅提升,從jdk6開始,它不再是現(xiàn)在的real-world程序中的重大問題。
在HotSpot中,大多的同步操作是由一種被稱作“fast-path”(快路)代碼的調(diào)用完成的。有兩種即時編譯器(JIT)和一個解釋器,它們都可以產(chǎn)生快路代碼。兩種編譯器分別是C1,即-client編譯器,以及C2,即-server編譯器。C1和C2均直接在同步點(diǎn)生成快路代碼。在一般沒有競態(tài)的情況下,同步操作將會完全在快路中執(zhí)行,然而當(dāng)發(fā)現(xiàn)需要去阻塞或者喚醒一個線程時(如monitorenter monitorexit),將會進(jìn)入slow-path執(zhí)行,它由本地C++代碼實(shí)現(xiàn)。
單個對象的同步狀態(tài)是在對象中的第一個word中編碼存放的(mark word,詳見前面的文章“54個JAVA官方文檔術(shù)語”)。mark word對同步狀態(tài)元數(shù)據(jù)來說是多用的(其實(shí)mark word本身也是多用的,它還包含gc分代數(shù)據(jù),對象的hash碼值)。這些狀態(tài)包含:
Neutral(中立): 未鎖
Biased(偏向): 鎖/未鎖+非共享
Stack-Locked(棧鎖): 鎖+共享 無競態(tài)
Inflated(膨脹鎖): 鎖/未鎖+共享和競態(tài)
9.線程管理
線程管理覆蓋線程從創(chuàng)建到銷毀的整個生命周期,并負(fù)責(zé)在vm內(nèi)協(xié)調(diào)各個線程。這個過程包含java代碼創(chuàng)建的線程(應(yīng)用代碼或庫代碼),綁定到vm的本地線程,出于各種目的創(chuàng)建的vm內(nèi)部線程。線程管理在絕大多數(shù)情況下是獨(dú)立于運(yùn)行平臺的,但仍有一些細(xì)節(jié)與所運(yùn)行的操作系統(tǒng)有所關(guān)聯(lián)。
線程模型
在hotspot虛擬機(jī)中,java線程和操作系統(tǒng)線程是一對一映射的關(guān)系,java線程即一個java.lang.Thread實(shí)例,當(dāng)它被開啟(start)后,本地線程也隨之創(chuàng)建,當(dāng)它終止(terminated)時,本地線程回收。操作系統(tǒng)負(fù)責(zé)調(diào)度所有的線程以及派發(fā)可用的cpu資源。java線程的優(yōu)先級以及操作系統(tǒng)線程的優(yōu)先級機(jī)制非常復(fù)雜,在不同的操作系統(tǒng)中表現(xiàn)也差異極大,此處略。
線程創(chuàng)建和銷毀
有兩種辦地可以向虛擬機(jī)中引入一個線程:執(zhí)行java.lang.Thread對象的start方法;或使用JNI將一個已存在的本地線程綁定到vm。出于一些目的,vm內(nèi)部也有一些辦法創(chuàng)建線程,本處不予討論。
在vm的一個線程上實(shí)際上關(guān)聯(lián)了若干個對象(HotSpot虛擬機(jī)是由面向?qū)ο蟮腸++實(shí)現(xiàn)),具體有:
a.java.lang.Thread實(shí)例表現(xiàn)java代碼中的一個線程。
b.JavaThread實(shí)例表示vm中的一個java.lang.Thread,JavaThread是Thread的子類,它包含額外的用以追蹤線程狀態(tài)的信息。一個JavaThread實(shí)例持有關(guān)聯(lián)的java.lang.Thread對象的引用(指針),同時持有OSThread實(shí)例的引用。java.lang.Thread也持有JavaThread的引用(以一個整數(shù)表示)。
c.OSThread(直譯為操作系統(tǒng)線程)實(shí)例表示了一個操作系統(tǒng)的線程,它包含額外的可用于追蹤線程狀態(tài)的操作系統(tǒng)級別的信息。OSThread包含了一個平臺指定的可用于定位真實(shí)操作系統(tǒng)線程的句柄。
當(dāng)java.lang.Thread實(shí)例啟動,vm創(chuàng)建關(guān)聯(lián)的JavaThread和OSThread對象,并最終創(chuàng)建了一個本地線程。在準(zhǔn)備好所有vm狀態(tài)后(如線程本地存儲,分配緩存,同步對象等)之后,本地線程得以啟動。本地線程完成初始化并執(zhí)行一個start-up方法,它會導(dǎo)向java.lang.Thread對象的run方法。隨后,在該方法返回或拋出未捕獲的異常時終止線程,并且在終止時與vm交互,這個過程是用以判斷是否它此時也需要終止vm。線程終止會釋放掉所有關(guān)聯(lián)的資源,從已知線程集中移除掉JavaThread實(shí)例,執(zhí)行OSThread實(shí)例和JavaThread實(shí)例的銷毀過程,并最終停止startup 方法的執(zhí)行。
可使用JNI調(diào)用AttachCurrentThread把本地線程綁定到虛擬機(jī)。作為此方法的響應(yīng),OSThread和JavaThread實(shí)例會被創(chuàng)建并進(jìn)行基本的初始化。接下來會使用綁定線程命令提供的參數(shù)及Thread類的構(gòu)造器反射初始化一個java線程。綁定完成后,線程可以通過可用的JNI方法調(diào)用所需的java代碼。當(dāng)本地線程不希望繼續(xù)在vm中進(jìn)行執(zhí)行時,可使用JNI調(diào)用DetachCurrentThread來解除與vm的關(guān)聯(lián)(會釋放資源,丟棄指向java.lang.Thread實(shí)例的引用,銷毀JavaThread和OSThread對象等)。
使用JNI調(diào)用CreateJavaVM創(chuàng)建vm是一個特殊的綁定本地線程的例子,它會由執(zhí)行器(java.c)完成或通過一個本地應(yīng)用來完成。這件事會造成一系列的初始化操作,也會在接下來出現(xiàn)類似執(zhí)行AttachCurrentThread的行為。隨后線程繼續(xù)執(zhí)行所需的java代碼(此例中即反射執(zhí)行main方法)
線程狀態(tài)
vm維護(hù)了一組內(nèi)部的線程狀態(tài)來標(biāo)識各線程的工作。協(xié)調(diào)各線程的交互,或當(dāng)線程執(zhí)行錯誤進(jìn)行debug時均需要用到這些狀態(tài)標(biāo)識。當(dāng)執(zhí)行了不同的動作時,線程的狀態(tài)可以發(fā)生改變,可即時使用這些轉(zhuǎn)換點(diǎn)檢測相應(yīng)的線程是否具備執(zhí)行將要執(zhí)行的動作的客觀條件,安全點(diǎn)即是一個典型的例子。
以虛擬機(jī)的視圖來看,線程有以下幾個狀態(tài):
_thread_new:表示一個新線程處于初始化的過程中。
_thread_in_Java:表示一個線程正在執(zhí)行java代碼。
_thread_in_vm:表示一個線程正在vm內(nèi)部執(zhí)行。
_thread_blocked:表示線程因某些原因阻塞(原因可能是正在獲取一個鎖,等待一個條件,sleep,執(zhí)行阻塞io等)。
出于debug的目的,可能需要一些額外的信息。如對于一些工具,或者用戶需要進(jìn)行線程棧轉(zhuǎn)儲或棧跡追蹤等操作時,均需要額外的信息,OSThread維護(hù)了相應(yīng)的一些信息,但部分信息現(xiàn)在已經(jīng)不再使用了,在線程轉(zhuǎn)儲時,可報告的狀態(tài)額外包含:
MONITOR_WAIT:表示線程正在等待獲取競態(tài)鎖。
CONDVAR_WAIT:表示線程正在等待vm使用的內(nèi)部條件變量(與java級別的對象無關(guān)聯(lián))。
OBJECT_WAIT:線程執(zhí)行了Object.wait方法。
虛擬機(jī)的其他子系統(tǒng)和庫可能維護(hù)了自己的狀態(tài)信息,如JVMTI工具,Thread類本身維護(hù)的ThreadState等。這些狀態(tài)一般不被其他組件使用。
虛擬機(jī)內(nèi)部線程
JAVA的執(zhí)行有著嚴(yán)格的步驟,不同于某些腳本語言,java即使運(yùn)行簡單的Hello World也需要相應(yīng)的資源準(zhǔn)備,因此,對于最簡單的Hello World,也可以發(fā)現(xiàn)系統(tǒng)中其實(shí)創(chuàng)建了若干個線程,它們主要是由vm中的線程和有關(guān)代碼庫中使用的線程(包含引用處理器,終結(jié)者線程等)組成。主要的虛擬機(jī)線程有以下幾種:
a.vm線程:它是VMThread的單例,負(fù)責(zé)執(zhí)行虛擬機(jī)操作。
b.周期任務(wù)線程:它是在vm內(nèi)部執(zhí)行周期操作的線程,是WatcherThread的實(shí)例。
c.GC線程:顧名思義。
d.編譯器線程:負(fù)責(zé)運(yùn)行時執(zhí)行字節(jié)碼到本地代碼的編譯。
e.信號派發(fā)線程:負(fù)責(zé)等待進(jìn)程信號并派發(fā)給java級別的信號處理方法。
以上所有線程是Thread類的實(shí)例,且所有執(zhí)行java代碼的線程均為JavaThread實(shí)例。vm內(nèi)部維護(hù)了一個Threads_list的數(shù)據(jù)結(jié)構(gòu),它是一個追蹤所有線程的鏈表,在vm內(nèi)部有一個核心的同步鎖Threads_lock,該鎖就用于保護(hù)Threads_list。
10.虛擬機(jī)操作和安全點(diǎn)
VMThread會監(jiān)測一個VMOperationQueue隊(duì)列,該隊(duì)列中存放的成員全部為“操作”,等待相應(yīng)的操作入隊(duì)后,它會執(zhí)行相應(yīng)的操作。這些操作被交給VMThread來執(zhí)行,因?yàn)樗鼈冃枰獀m到達(dá)安全點(diǎn)才可執(zhí)行。簡單來說,當(dāng)vm在到達(dá)安全點(diǎn)時,所有vm內(nèi)運(yùn)行的線程均會阻塞,所有在本地代碼中執(zhí)行的線程在安全點(diǎn)期間被禁止返回vm執(zhí)行。這意味著虛擬機(jī)操作可以在已知無線程處于正在更改java堆的前提下進(jìn)行運(yùn)行,且此時所有的線程處在一個特殊的,不改變java棧的可檢視狀態(tài)。
最著名的虛擬機(jī)操作之一即gc,或者更精確一點(diǎn)是很多gc算法中的“stop the world”階段,但也存在很多基于安全點(diǎn)的其他操作,作者在“54個java官方文檔術(shù)語”一文中簡單列舉了這些操作。
很多虛擬機(jī)操作是同步阻塞的,請求者會阻塞到操作完成,但也有一些異步并發(fā)的操作,請求者可以和VMThread并行執(zhí)行。
安全點(diǎn)是使用協(xié)作輪詢的機(jī)制初始化的。簡單來說,線程會去詢問“我是否要為一個安全點(diǎn)阻塞”。這個詢問機(jī)制的實(shí)現(xiàn)并不簡單。當(dāng)發(fā)生線程的狀態(tài)轉(zhuǎn)換時會常見詢問這個問題,但并是所有的狀態(tài)轉(zhuǎn)換都會詢問,如當(dāng)一個線程離開vm并進(jìn)入native代碼塊時。當(dāng)從編譯的代碼返回時,或在循環(huán)迭代的階段,線程也會詢問這個問題。對于執(zhí)行解釋代碼的線程來說是不常詢問的,但在安全點(diǎn),它也有相應(yīng)的方案,當(dāng)請求安全點(diǎn)時,解釋器會切換到一個包含了該詢問的代碼的轉(zhuǎn)發(fā)表,當(dāng)安全點(diǎn)結(jié)束后,從派發(fā)表切回。一旦請求了安全點(diǎn),VMThread必須等到所有已知線程均處于安全點(diǎn)-安全狀態(tài),然后才可執(zhí)行虛擬機(jī)操作。在安全點(diǎn)期間,使用Threads_lock來block住那些正在運(yùn)行的線程,虛擬機(jī)操作完成后,VMThread釋放該鎖。
11.C++堆管理
除了由JAVA堆管理者和gc維護(hù)的JAVA堆以外,HotSpot虛擬機(jī)也使用一個c/c++堆(即所謂的分配堆)來存放虛擬機(jī)的內(nèi)部對象和數(shù)據(jù)。這些用來管理C++堆操作的類都由一個基類Arena(競技場)派生而來。
Arena和它的子類提供了位于分配/釋放機(jī)制頂層的一個快速分配層。每一個Arena在3個全局的塊池(ChunkPools)中進(jìn)行內(nèi)存塊(Chunk)的分配。不同的塊池滿足不同大小區(qū)間的分配,舉例說明,如果請求分配1k的內(nèi)存,那么會用“small”塊池分配,如果請求分配10k內(nèi)存,則使用“medium”塊池,這樣可以避免內(nèi)存碎片浪費(fèi)。
Arena系統(tǒng)也提供了比純粹的分配/釋放機(jī)制更佳的性能。因?yàn)楹笳呖赡苄枰@取一個操作系統(tǒng)的全局鎖,它會嚴(yán)重影響擴(kuò)展性并傷害系統(tǒng)性能。Arena是一些緩存了指定內(nèi)存數(shù)量的線程本地對象,這樣的設(shè)計使得它可以在分配時使用“快路”分配而不用獲取該全局鎖,對于釋放內(nèi)存的操作,通常情況下Arena不需要獲得鎖。
Arena的兩個子類,ResourceArena應(yīng)用于線程本地資源管理,HandleArena用于句柄管理,在client和server編譯器中均用到了這兩種arena。
12.JAVA本地接口(JNI)
JNI代表本地程序接口。它允許運(yùn)行在jvm中的java代碼與使用其他語言(如c/c++)實(shí)現(xiàn)的應(yīng)用或庫進(jìn)行交互。JNI本地方法可以用來做很多事情,如創(chuàng)建對象,檢視對象,更新對象,調(diào)用java方法,捕獲拋出的異常,加載類和獲取類信息,執(zhí)行運(yùn)行時類型檢測等。JNI也可以使用Invocation api來啟用jvm中嵌入的任意native應(yīng)用,通過它,我們可以輕易地讓已有應(yīng)用可以用java運(yùn)行而不用去鏈接vm源碼。
但有重要的一點(diǎn),一旦使用了JNI,便失去了使用java平臺的兩個重要的好處。
第一,依賴jni的java應(yīng)用不保證能在多平臺上可用,盡管基于java實(shí)現(xiàn)的部分是可以跨宿主機(jī)環(huán)境的,使用本地程序語言實(shí)現(xiàn)的部分仍舊需要重新編譯。
第二,使用java語言編寫的程序是類型安全的,C或者C++則不是。結(jié)果就是使用了JNI的程序員必須額外注意這部分代碼,行為不端的本地方法可能擾亂整個應(yīng)用,出于這個原因考慮,在執(zhí)行jni功能前,相應(yīng)使用到j(luò)ni的應(yīng)用一定要負(fù)責(zé)它的安全性檢查。
原則上講,應(yīng)盡可能少地使用本地方法,并做好這部分代碼與java應(yīng)用的隔離,作者看來,unsafe后門包是一個典型的案例。
在HotSpot虛擬機(jī)中,jni方法的實(shí)現(xiàn)相對直接,它使用各種vm內(nèi)部原生規(guī)則來執(zhí)行諸如對象創(chuàng)建方法調(diào)用等行為,通常情況,相應(yīng)的如解釋器等子系統(tǒng)也使用了這些運(yùn)行時規(guī)則。
可使用命令行選項(xiàng)-Xcheck:jni來幫助debug那些使用了本地方法的應(yīng)用,該選項(xiàng)會使得JNI調(diào)用時用到一組debug接口。這些接口會更加嚴(yán)格地進(jìn)行JNI調(diào)用的參數(shù)驗(yàn)證,同時還會做一些額外的內(nèi)部一致性檢查。
HotSpot對于執(zhí)行本地方法的線程進(jìn)行了額外“照顧”,對于一些vm的工作,比如gc過程中,一部分線程必須保證在安全點(diǎn)阻塞,從而保證java堆在這些敏感過程中不會再次更改。當(dāng)我們希望把一個安全點(diǎn)上的線程帶入到本地代碼執(zhí)行時,它會被允許進(jìn)入本地方法,但是禁止從該方法返回java代碼或者執(zhí)行JNI調(diào)用。
13.虛擬機(jī)致命故障處理
毫無疑問,提供致命故障的處理對jvm來說是非常之必需的。以oom為例,它是一個典型的致命錯誤。當(dāng)發(fā)生這類錯誤時,一定要給用戶提供一些合理且友好的方式來理解致命錯誤成因,從而能快速修復(fù)問題,這方面的問題不僅包含應(yīng)用本身,也包含jvm本身。
第一,一般當(dāng)jvm在致命故障發(fā)生時crash掉,它會轉(zhuǎn)儲一個hotspot的錯誤日志文件,格式為:hs_err_pid
第二,也可以使用-XX:ErrorFile=選項(xiàng)來指定錯誤日志的位置。
第三,發(fā)生oom時,也會觸發(fā)生成該錯誤文件。
還有一個重要的功能,可以指定一個選項(xiàng):-XX:OnError="cmd1 args...;com2 ...",這樣當(dāng)發(fā)生了crash時會執(zhí)行這些指令,相應(yīng)的指令就比較自由,比如我們可以指定此時執(zhí)行一些諸如dbx或Windbg之類的debugger執(zhí)行相應(yīng)的操作。早于jdk6的應(yīng)用可使用-XX:+ShowMessageBoxOnError來指定發(fā)生crash時使用的debugger。以下是jvm內(nèi)部處理致命錯誤的一些摘要:
首先,用VMError類聚合和轉(zhuǎn)儲hs_err_pid
第二,vm使用信號來進(jìn)行內(nèi)部的交流,當(dāng)出現(xiàn)未識別的信號,致命錯誤處理器被執(zhí)行。而這個信號可能源自一個應(yīng)用的jni代碼,操作系統(tǒng)本地庫,jre本地庫,甚至是jvm本身。
第三,致命錯誤處理器是慎重編寫的,這也是為了避免它自己也出現(xiàn)錯誤,比如在出現(xiàn)StackOverFlow時,或在持有重要的鎖期間發(fā)生crash(如持有分配鎖)。
死鎖是一種常見的錯誤,一般發(fā)生在應(yīng)用程序在申請多個鎖時順序不正確的情況。當(dāng)死鎖發(fā)生時,找出相應(yīng)的點(diǎn)也是比較困難的,此時可以抓出java進(jìn)程id,發(fā)送SIGQUIT到該進(jìn)程(Solaris/Linux),會在標(biāo)準(zhǔn)輸出中輸出java級別的棧信息,這對分析死鎖幫助極大,不過在jdk6之上的版本,已經(jīng)可以使用Jconsole來輕松處理該問題。
順便簡單提一提除了Jconsole/VisualVM等集成工具之外,一些單一目的的自帶工具。
jps:jvm進(jìn)程工具,可以查看各jvm進(jìn)程,名稱和編號。
jstat:虛擬機(jī)統(tǒng)計信息。比如發(fā)生了多少次full gc等。
jinfo:java配置信息工具,運(yùn)行時查看jvm進(jìn)程的配置。
jmap:內(nèi)存映像工具,可以將當(dāng)前內(nèi)存情況轉(zhuǎn)儲一個快照文件。
jhat:堆轉(zhuǎn)儲快照分析工具。
jstack:java堆棧跟蹤工具。
總結(jié)
本文簡述了包含運(yùn)行時參數(shù)處理,線程管理,類加載,類數(shù)據(jù)共享,運(yùn)行時編譯,異常處理,重大錯誤處理等java運(yùn)行時技術(shù)。參考資料主要源自官方的若干文檔,一部分資料是專屬性的,如專門描述JVM或JIT,但根本無法確定成作于哪個版本(關(guān)于cds作者判斷與JAVA8中的描述相同,但顯然早已不適用),一部分資料是依托于較新版本的,因?yàn)樾屡f版本的文檔并未保持同一目錄結(jié)構(gòu),有些組件未能在新版中找到詳盡的文檔,因此難免會有不準(zhǔn)確或過時的內(nèi)容,作者爭取在后面找到最新且更加權(quán)威的資料以修正。
作者個人認(rèn)為有兩點(diǎn)重要的收獲,一是宏觀上了解了官方出品的HotSpot虛擬機(jī)在運(yùn)行時的框架設(shè)計,理解java在運(yùn)行時為我們竭盡全力做了哪些事;二是了解某些具體模塊在新版中的優(yōu)化和取舍,從而間接了解接下來java的使用趨勢。如“云友好”,“多適應(yīng)”,“開放”等。
這三點(diǎn)是作者個人不成熟的簡單總結(jié),寫到這里,也順便對這三點(diǎn)進(jìn)行一個“簡單總結(jié)”。
云友好其實(shí)體現(xiàn)的方面很多,cds就是重要一點(diǎn),它在最新幾個版本的更新用一句俗化表示:幫用戶省錢。G1定時釋放無用內(nèi)存的新特性也體現(xiàn)了這一點(diǎn)。
多適應(yīng)和開放也很好理解,不止是gc方面,前面簡單提過的zgc等針對超大堆的gc,以及G1這種放權(quán)讓用戶指定目標(biāo)的gc,綜合此前的各種gc,基本涵蓋了我們所有可能的應(yīng)用環(huán)境。同樣的,模塊化系統(tǒng)也天然匹配了中小型到大型項(xiàng)目的需求,一個項(xiàng)目從初創(chuàng)到逐漸壯大,或許最終就是模塊不斷擴(kuò)充的過程,模塊化系統(tǒng)甚至允許對jdk本身進(jìn)行按需定制,對于小型設(shè)備用戶也無益于是一個福音。JIT本身就具備自適應(yīng)的編譯思想,最優(yōu)化最常執(zhí)行的代碼,graal是新出的基于java的JIT編譯器。同步機(jī)制也引入了“自適應(yīng)”自旋鎖,G1中對cs的選擇也具備自適應(yīng)性等。
再一次,膜拜前輩。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/74922.html
摘要:由于的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是字節(jié)的整數(shù)倍,換句話說,就是對象的大小必須是字節(jié)的整數(shù)倍。對象大小計算要點(diǎn)在位系統(tǒng)下,存放指針的空間大小是字節(jié),是字節(jié),對象頭為字節(jié)。靜態(tài)屬性不算在對象大小內(nèi)。 jvm系列 垃圾回收基礎(chǔ) JVM的編譯策略 GC的三大基礎(chǔ)算法 GC的三大高級算法 GC策略的評價指標(biāo) JVM信息查看 GC通用日志解讀 jvm的card table數(shù)據(jù)結(jié)構(gòu) Ja...
摘要:如同其它虛擬機(jī),虛擬機(jī)為字節(jié)碼提供了一個運(yùn)行時環(huán)境。編譯是一個混合模式的虛擬機(jī),也就是說它既可以解釋字節(jié)碼,又可以將代碼編譯為本地機(jī)器碼以更快的執(zhí)行。解決此問題一般是在進(jìn)程啟動后,對代碼進(jìn)行預(yù)熱以使它們被強(qiáng)制編譯。 Java HotSpot虛擬機(jī)是Oracle收購Sun時獲得的,JVM和開源的OpenJDK都是以此虛擬機(jī)為基礎(chǔ)發(fā)展的。如同其它虛擬機(jī),HotSpot虛擬機(jī)為字節(jié)碼提供了一...
學(xué)習(xí)JVM的相關(guān)資料 《深入理解Java虛擬機(jī)——JVM高級特性與最佳實(shí)踐(第2版)》 showImg(https://segmentfault.com/img/bVbsqF5?w=200&h=200); 基于最新JDK1.7,圍繞內(nèi)存管理、執(zhí)行子系統(tǒng)、程序編譯與優(yōu)化、高效并發(fā)等核心主題對JVM進(jìn)行全面而深入的分析,深刻揭示JVM的工作原理。以實(shí)踐為導(dǎo)向,通過大量與實(shí)際生產(chǎn)環(huán)境相結(jié)合的案例展示了解...
摘要:對字節(jié)碼文件進(jìn)行解釋執(zhí)行,把字節(jié)碼翻譯成相關(guān)平臺上的機(jī)器指令。使用命令可對字節(jié)碼文件以及配置文件進(jìn)行打包可對一個由多個字節(jié)碼文件和配置文件等資源文件構(gòu)成的項(xiàng)目進(jìn)行打包。和不存在永久代這種說法。 Java技術(shù)體系 從廣義上講,Clojure、JRuby、Groovy等運(yùn)行于Java虛擬機(jī)上的語言及其相關(guān)的程序都屬于Java技術(shù)體系中的一員。如果僅從傳統(tǒng)意義上來看,Sun官方所定義的Jav...
摘要:編譯參見深入理解虛擬機(jī)節(jié)走進(jìn)之一自己編譯源碼內(nèi)存模型運(yùn)行時數(shù)據(jù)區(qū)域根據(jù)虛擬機(jī)規(guī)范的規(guī)定,的內(nèi)存包括以下幾個運(yùn)運(yùn)行時數(shù)據(jù)區(qū)域程序計數(shù)器程序計數(shù)器是一塊較小的內(nèi)存空間,他可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。 點(diǎn)擊進(jìn)入我的博客 1.1 基礎(chǔ)知識 1.1.1 一些基本概念 JDK(Java Development Kit):Java語言、Java虛擬機(jī)、Java API類庫JRE(...
閱讀 940·2021-09-27 13:36
閱讀 888·2021-09-08 09:35
閱讀 1064·2021-08-12 13:25
閱讀 1437·2019-08-29 16:52
閱讀 2907·2019-08-29 15:12
閱讀 2726·2019-08-29 14:17
閱讀 2606·2019-08-26 13:57
閱讀 1012·2019-08-26 13:51