JVM經典面試20問
今天給大家分享20道JVM常考的面試題,答案也整理好了,不會的快快查漏補缺~
以下是本期JVM面試題的目錄:
講一下JVM的內存結構? 說一下堆棧的區(qū)別? 什么情況下會發(fā)生棧溢出? 類文件結構 什么是類加載?類加載的過程? 什么是雙親委派模型? 為什么需要雙親委派模型? 什么是類加載器,類加載器有哪些? 如何判斷一個對象是否存活? 可作為GC Roots的對象有哪些? 什么情況下類會被卸載? 強引用、軟引用、弱引用、虛引用是什么? Minor GC 和 Full GC的區(qū)別? 內存的分配策略? 垃圾回收算法有哪些? 有哪些垃圾回收器? 常用的 JVM 調優(yōu)的參數(shù)都有哪些? JVM調優(yōu)工具有哪些? main方法的執(zhí)行過程? 對象的創(chuàng)建過程?
講一下JVM的內存結構?
JVM內存結構分為5大區(qū)域,程序計數(shù)器、虛擬機棧、本地方法棧、堆、方法區(qū)。

程序計數(shù)器
線程私有的,作為當前線程的行號指示器,用于記錄當前虛擬機正在執(zhí)行的線程指令地址。程序計數(shù)器主要有兩個作用:
當前線程所執(zhí)行的字節(jié)碼的行號指示器,通過它實現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。 在多線程的情況下,程序計數(shù)器用于記錄當前線程執(zhí)行的位置,當線程被切換回來的時候能夠知道它上次執(zhí)行的位置。
程序計數(shù)器是唯一一個不會出現(xiàn) OutOfMemoryError 的內存區(qū)域,它的生命周期隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的結束而死亡。
虛擬機棧
Java 虛擬機棧是由一個個棧幀組成,而每個棧幀中都擁有:局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口信息。每一次函數(shù)調用都會有一個對應的棧幀被壓入虛擬機棧,每一個函數(shù)調用結束后,都會有一個棧幀被彈出。
局部變量表是用于存放方法參數(shù)和方法內的局部變量。
每個棧幀都包含一個指向運行時常量池中該棧所屬方法的符號引用,在方法調用過程中,會進行動態(tài)鏈接,將這個符號引用轉化為直接引用。
部分符號引用在類加載階段的時候就轉化為直接引用,這種轉化就是靜態(tài)鏈接 部分符號引用在運行期間轉化為直接引用,這種轉化就是動態(tài)鏈接
Java 虛擬機棧也是線程私有的,每個線程都有各自的 Java 虛擬機棧,而且隨著線程的創(chuàng)建而創(chuàng)建,隨著線程的死亡而死亡。Java 虛擬機棧會出現(xiàn)兩種錯誤:StackOverFlowError 和 OutOfMemoryError。
可以通過-Xss參數(shù)來指定每個線程的虛擬機棧內存大小:
java?-Xss2M
本地方法棧
虛擬機棧為虛擬機執(zhí)行 Java 方法服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。Native 方法一般是用其它語言(C、C++等)編寫的。
本地方法被執(zhí)行的時候,在本地方法棧也會創(chuàng)建一個棧幀,用于存放該本地方法的局部變量表、操作數(shù)棧、動態(tài)鏈接、出口信息。
堆
堆用于存放對象實例,是垃圾收集器管理的主要區(qū)域,因此也被稱作GC堆。堆可以細分為:新生代(Eden空間、From Survivor、To Survivor空間)和老年代。
通過 -Xms設定程序啟動時占用內存大小,通過-Xmx設定程序運行期間最大可占用的內存大小。如果程序運行需要占用更多的內存,超出了這個設置值,就會拋出OutOfMemory異常。
java?-Xms1M?-Xmx2M
方法區(qū)
方法區(qū)與 Java 堆一樣,是各個線程共享的內存區(qū)域,它用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
對方法區(qū)進行垃圾回收的主要目標是對常量池的回收和對類的卸載。
永久代
方法區(qū)是 JVM 的規(guī)范,而永久代PermGen是方法區(qū)的一種實現(xiàn)方式,并且只有 HotSpot 有永久代。對于其他類型的虛擬機,如JRockit沒有永久代。由于方法區(qū)主要存儲類的相關信息,所以對于動態(tài)生成類的場景比較容易出現(xiàn)永久代的內存溢出。
元空間
JDK 1.8 的時候,HotSpot的永久代被徹底移除了,使用元空間替代。元空間的本質和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。兩者最大的區(qū)別在于:元空間并不在虛擬機中,而是使用直接內存。
說一下堆棧的區(qū)別?
堆的物理地址分配是不連續(xù)的,性能較慢;棧的物理地址分配是連續(xù)的,性能相對較快。
堆存放的是對象的實例和數(shù)組;棧存放的是局部變量,操作數(shù)棧,返回結果等。
堆是線程共享的;棧是線程私有的。
什么情況下會發(fā)生棧溢出?
當線程請求的棧深度超過了虛擬機允許的最大深度時,會拋出 StackOverFlowError異常。這種情況通常是因為方法遞歸沒終止條件。新建線程的時候沒有足夠的內存去創(chuàng)建對應的虛擬機棧,虛擬機會拋出 OutOfMemoryError異常。比如線程啟動過多就會出現(xiàn)這種情況。
類文件結構
Class 文件結構如下:
ClassFile?{
????u4?????????????magic;?//類文件的標志
????u2?????????????minor_version;//小版本號
????u2?????????????major_version;//大版本號
????u2?????????????constant_pool_count;//常量池的數(shù)量
????cp_info????????constant_pool[constant_pool_count-1];//常量池
????u2?????????????access_flags;//類的訪問標記
????u2?????????????this_class;//當前類的索引
????u2?????????????super_class;//父類
????u2?????????????interfaces_count;//接口
????u2?????????????interfaces[interfaces_count];//一個類可以實現(xiàn)多個接口
????u2?????????????fields_count;//字段屬性
????field_info?????fields[fields_count];//一個類會可以有個字段
????u2?????????????methods_count;//方法數(shù)量
????method_info????methods[methods_count];//一個類可以有個多個方法
????u2?????????????attributes_count;//此類的屬性表中的屬性數(shù)
????attribute_info?attributes[attributes_count];//屬性表集合
}
主要參數(shù)如下:
魔數(shù):class文件標志。
文件版本:高版本的 Java 虛擬機可以執(zhí)行低版本編譯器生成的類文件,但是低版本的 Java 虛擬機不能執(zhí)行高版本編譯器生成的類文件。
常量池:存放字面量和符號引用。字面量類似于 Java 的常量,如字符串,聲明為final的常量值等。符號引用包含三類:類和接口的全限定名,方法的名稱和描述符,字段的名稱和描述符。
訪問標志:識別類或者接口的訪問信息,比如這個Class是類還是接口,是否為 public 或者 abstract 類型等等。
當前類的索引:類索引用于確定這個類的全限定名。
什么是類加載?類加載的過程?
類的加載指的是將類的class文件中的二進制數(shù)據(jù)讀入到內存中,將其放在運行時數(shù)據(jù)區(qū)的方法區(qū)內,然后在堆區(qū)創(chuàng)建一個此類的對象,通過這個對象可以訪問到方法區(qū)對應的類信息。

加載
通過類的全限定名獲取定義此類的二進制字節(jié)流 將字節(jié)流所代表的靜態(tài)存儲結構轉換為方法區(qū)的運行時數(shù)據(jù)結構 在內存中生成一個代表該類的 Class對象,作為方法區(qū)類信息的訪問入口
驗證
確保Class文件的字節(jié)流中包含的信息符合虛擬機規(guī)范,保證在運行后不會危害虛擬機自身的安全。主要包括四種驗證:文件格式驗證,元數(shù)據(jù)驗證,字節(jié)碼驗證,符號引用驗證。
準備
為類變量分配內存并設置類變量初始值的階段。
解析
虛擬機將常量池內的符號引用替換為直接引用的過程。符號引用用于描述目標,直接引用直接指向目標的地址。
初始化
開始執(zhí)行類中定義的Java代碼,初始化階段是調用類構造器的過程。
什么是雙親委派模型?
一個類加載器收到一個類的加載請求時,它首先不會自己嘗試去加載它,而是把這個請求委派給父類加載器去完成,這樣層層委派,因此所有的加載請求最終都會傳送到頂層的啟動類加載器中,只有當父類加載器反饋自己無法完成這個加載請求時,子加載器才會嘗試自己去加載。

雙親委派模型的具體實現(xiàn)代碼在 java.lang.ClassLoader中,此類的 loadClass() 方法運行過程如下:先檢查類是否已經加載過,如果沒有則讓父類加載器去加載。當父類加載器加載失敗時拋出 ClassNotFoundException,此時嘗試自己去加載。源碼如下:
public?abstract?class?ClassLoader?{
????//?The?parent?class?loader?for?delegation
????private?final?ClassLoader?parent;
????public?Class>?loadClass(String?name)?throws?ClassNotFoundException?{
????????return?loadClass(name,?false);
????}
????protected?Class>?loadClass(String?name,?boolean?resolve)?throws?ClassNotFoundException?{
????????synchronized?(getClassLoadingLock(name))?{
????????????//?First,?check?if?the?class?has?already?been?loaded
????????????Class>?c?=?findLoadedClass(name);
????????????if?(c?==?null)?{
????????????????try?{
????????????????????if?(parent?!=?null)?{
????????????????????????//把類加載請求委派給父類加載器去完成
????????????????????????c?=?parent.loadClass(name,?false);
????????????????????}?else?{
????????????????????????c?=?findBootstrapClassOrNull(name);
????????????????????}
????????????????}?catch?(ClassNotFoundException?e)?{
????????????????????//?ClassNotFoundException?thrown?if?class?not?found
????????????????????//?from?the?non-null?parent?class?loader
????????????????}
????????????????if?(c?==?null)?{
????????????????????//?If?still?not?found,?then?invoke?findClass?in?order
????????????????????//?to?find?the?class.
????????????????????c?=?findClass(name);
????????????????}
????????????}
????????????if?(resolve)?{
????????????????resolveClass(c);
????????????}
????????????return?c;
????????}
????}
????protected?Class>?findClass(String?name)?throws?ClassNotFoundException?{
????????throw?new?ClassNotFoundException(name);
????}
}
為什么需要雙親委派模型?
雙親委派模型的好處:可以防止內存中出現(xiàn)多份同樣的字節(jié)碼。如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個java.lang.Object的同名類并放在ClassPath中,多個類加載器都去加載這個類到內存中,系統(tǒng)中將會出現(xiàn)多個不同的Object類,那么類之間的比較結果及類的唯一性將無法保證。
什么是類加載器,類加載器有哪些?
實現(xiàn)通過類的全限定名獲取該類的二進制字節(jié)流的代碼塊叫做類加載器。
主要有以下四種類加載器:
啟動類加載器:用來加載 Java 核心類庫,無法被 Java 程序直接引用。 擴展類加載器:它用來加載 Java 的擴展庫。Java 虛擬機的實現(xiàn)會提供一個擴展庫目錄。該類加載器在此目錄里面查找并加載 Java 類。 系統(tǒng)類加載器:它根據(jù)應用的類路徑來加載 Java 類。可通過 ClassLoader.getSystemClassLoader()獲取它。自定義類加載器:通過繼承 java.lang.ClassLoader類的方式實現(xiàn)。
如何判斷一個對象是否存活?
對堆垃圾回收前的第一步就是要判斷那些對象已經死亡(即不再被任何途徑引用的對象)。判斷對象是否存活有兩種方法:引用計數(shù)法和可達性分析。
引用計數(shù)法
給對象中添加一個引用計數(shù)器,每當有一個地方引用它,計數(shù)器就加 1;當引用失效,計數(shù)器就減 1;任何時候計數(shù)器為 0 的對象就是不可能再被使用的。
這種方法很難解決對象之間相互循環(huán)引用的問題。比如下面的代碼,obj1 和 obj2 互相引用,這種情況下,引用計數(shù)器的值都是1,不會被垃圾回收。
public?class?ReferenceCount?{
????Object?instance?=?null;
?public?static?void?main(String[]?args)?{
??ReferenceCount?obj1?=?new?ReferenceCount();
??ReferenceCount?obj2?=?new?ReferenceCount();
??obj1.instance?=?obj2;
??obj2.instance?=?obj1;
??obj1?=?null;
??obj2?=?null;
?}
}
可達性分析
通過GC Root對象為起點,從這些節(jié)點向下搜索,搜索所走過的路徑叫引用鏈,當一個對象到GC Root沒有任何的引用鏈相連時,說明這個對象是不可用的。

可作為GC Roots的對象有哪些?
虛擬機棧中引用的對象 本地方法棧中Native方法引用的對象 方法區(qū)中類靜態(tài)屬性引用的對象 方法區(qū)中常量引用的對象
什么情況下類會被卸載?
需要同時滿足以下 3 個條件,類才可能會被卸載 :
該類所有的實例都已經被回收。 加載該類的類加載器已經被回收。 該類對應的 java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機可以對滿足上述 3 個條件的類進行回收,但不一定會進行回收。
強引用、軟引用、弱引用、虛引用是什么?
強引用:在程序中普遍存在的引用賦值,類似Object obj = new Object()這種引用關系。只要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象。
軟引用:如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。
//軟引用
SoftReference?softRef?=?new?SoftReference(str);
弱引用:在進行垃圾回收時,不管當前內存空間足夠與否,都會回收只具有弱引用的對象。
//弱引用
WeakReference?weakRef?=?new?WeakReference(str);
虛引用:虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛引用主要是為了能在對象被收集器回收時收到一個系統(tǒng)通知。
Minor GC 和 Full GC的區(qū)別?
Minor GC:回收新生代,因為新生代對象存活時間很短,因此
Minor GC會頻繁執(zhí)行,執(zhí)行的速度一般也會比較快。Full GC:回收老年代和新生代,老年代的對象存活時間長,因此
Full GC很少執(zhí)行,執(zhí)行速度會比Minor GC慢很多。
內存的分配策略?
對象優(yōu)先在 Eden 分配
大多數(shù)情況下,對象在新生代 Eden 上分配,當 Eden 空間不夠時,觸發(fā) Minor GC。
大對象直接進入老年代
大對象是指需要連續(xù)內存空間的對象,最典型的大對象有長字符串和大數(shù)組。可以設置JVM參數(shù) -XX:PretenureSizeThreshold,大于此值的對象直接在老年代分配。
長期存活的對象進入老年代
通過參數(shù) -XX:MaxTenuringThreshold 可以設置對象進入老年代的年齡閾值。對象在Survivor區(qū)每經過一次 Minor GC,年齡就增加 1 歲,當它的年齡增加到一定程度,就會被晉升到老年代中。
動態(tài)對象年齡判定
并非對象的年齡必須達到 MaxTenuringThreshold 才能晉升老年代,如果在 Survivor 中相同年齡所有對象大小的總和大于 Survivor 空間的一半,則年齡大于或等于該年齡的對象可以直接進入老年代,無需達到 MaxTenuringThreshold 年齡閾值。
空間分配擔保
在發(fā)生 Minor GC 之前,虛擬機先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果條件成立的話,那么 Minor GC 是安全的。如果不成立的話虛擬機會查看 HandlePromotionFailure 的值是否允許擔保失敗。如果允許,那么就會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值為不允許擔保失敗,那么就要進行一次 Full GC。
垃圾回收算法有哪些?
垃圾回收算法有四種,分別是標記清除法、標記整理法、復制算法、分代收集算法。
標記清除算法
首先利用可達性去遍歷內存,把存活對象和垃圾對象進行標記。標記結束后統(tǒng)一將所有標記的對象回收掉。這種垃圾回收算法效率較低,并且會產生大量不連續(xù)的空間碎片。

復制清除算法
半區(qū)復制,用于新生代垃圾回收。將內存分為大小相同的兩塊,每次使用其中的一塊。當這一塊的內存使用完后,就將還存活的對象復制到另一塊去,然后再把使用的空間一次清理掉。
特點:實現(xiàn)簡單,運行高效,但可用內存縮小為了原來的一半,浪費空間。
標記整理算法
根據(jù)老年代的特點提出的一種標記算法,標記過程仍然與標記-清除算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉邊界以外的內存。

分類收集算法
根據(jù)各個年代的特點采用最適當?shù)氖占惴ā?/p>
一般將堆分為新生代和老年代。
新生代使用復制算法 老年代使用標記清除算法或者標記整理算法
在新生代中,每次垃圾收集時都有大批對象死去,只有少量存活,使用復制算法比較合適,只需要付出少量存活對象的復制成本就可以完成收集。老年代對象存活率高,適合使用標記-清理或者標記-整理算法進行垃圾回收。
有哪些垃圾回收器?
垃圾回收器主要分為以下幾種:Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1。
Serial 收集器
單線程收集器,使用一個垃圾收集線程去進行垃圾回收,在進行垃圾回收的時候必須暫停其他所有的工作線程( Stop The World ),直到它收集結束。
特點:簡單高效;內存消耗小;沒有線程交互的開銷,單線程收集效率高;需暫停所有的工作線程,用戶體驗不好。
ParNew 收集器
Serial收集器的多線程版本,除了使用多線程進行垃圾收集外,其他行為、參數(shù)與 Serial 收集器基本一致。
Parallel Scavenge 收集器
新生代收集器,基于復制清除算法實現(xiàn)的收集器。特點是吞吐量優(yōu)先,能夠并行收集的多線程收集器,允許多個垃圾回收線程同時運行,降低垃圾收集時間,提高吞吐量。所謂吞吐量就是 CPU 中用于運行用戶代碼的時間與 CPU 總消耗時間的比值(吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間))。Parallel Scavenge 收集器關注點是吞吐量,高效率的利用 CPU 資源。CMS 垃圾收集器關注點更多的是用戶線程的停頓時間。
Parallel Scavenge收集器提供了兩個參數(shù)用于精確控制吞吐量,分別是控制最大垃圾收集停頓時間的-XX:MaxGCPauseMillis參數(shù)以及直接設置吞吐量大小的-XX:GCTimeRatio參數(shù)。
-XX:MaxGCPauseMillis參數(shù)的值是一個大于0的毫秒數(shù),收集器將盡量保證內存回收花費的時間不超過用戶設定值。-XX:GCTimeRatio參數(shù)的值大于0小于100,即垃圾收集時間占總時間的比率,相當于吞吐量的倒數(shù)。
Serial Old 收集器
Serial 收集器的老年代版本,單線程收集器,使用標記整理算法。
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。多線程垃圾收集,使用標記整理算法。
CMS 收集器
Concurrent Mark Sweep ,并發(fā)標記清除,追求獲取最短停頓時間,實現(xiàn)了讓垃圾收集線程與用戶線程基本上同時工作。
CMS 垃圾回收基于標記清除算法實現(xiàn),整個過程分為四個步驟:
初始標記:暫停所有用戶線程( Stop The World),記錄直接與GC Roots直接相連的對象 。并發(fā)標記:從 GC Roots開始對堆中對象進行可達性分析,找出存活對象,耗時較長,但是不需要停頓用戶線程。重新標記:在并發(fā)標記期間對象的引用關系可能會變化,需要重新進行標記。此階段也會暫停所有用戶線程。 并發(fā)清除:清除標記對象,這個階段也是可以與用戶線程同時并發(fā)的。
在整個過程中,耗時最長的是并發(fā)標記和并發(fā)清除階段,這兩個階段垃圾收集線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程是與用戶線程一起并發(fā)執(zhí)行的。
優(yōu)點:并發(fā)收集,停頓時間短。
缺點:
標記清除算法導致收集結束有大量空間碎片。 產生浮動垃圾,在并發(fā)清理階段用戶線程還在運行,會不斷有新的垃圾產生,這一部分垃圾出現(xiàn)在標記過程之后, CMS無法在當次收集中回收它們,只好等到下一次垃圾回收再處理;
G1收集器
G1垃圾收集器的目標是在不同應用場景中追求高吞吐量和低停頓之間的最佳平衡。
G1將整個堆分成相同大小的分區(qū)(Region),有四種不同類型的分區(qū):Eden、Survivor、Old和Humongous。分區(qū)的大小取值范圍為 1M 到 32M,都是2的冪次方。分區(qū)大小可以通過-XX:G1HeapRegionSize參數(shù)指定。Humongous區(qū)域用于存儲大對象。G1規(guī)定只要大小超過了一個分區(qū)容量一半的對象就認為是大對象。

G1 收集器對各個分區(qū)回收所獲得的空間大小和回收所需時間的經驗值進行排序,得到一個優(yōu)先級列表,每次根據(jù)用戶設置的最大回收停頓時間,優(yōu)先回收價值最大的分區(qū)。
特點:可以由用戶指定期望的垃圾收集停頓時間。
G1 收集器的回收過程分為以下幾個步驟:
初始標記。暫停所有其他線程,記錄直接與 GC Roots直接相連的對象,耗時較短 。并發(fā)標記。從 GC Roots開始對堆中對象進行可達性分析,找出要回收的對象,耗時較長,不過可以和用戶程序并發(fā)執(zhí)行。最終標記。需對其他線程做短暫的暫停,用于處理并發(fā)標記階段對象引用出現(xiàn)變動的區(qū)域。 篩選回收。對各個分區(qū)的回收價值和成本進行排序,根據(jù)用戶所期望的停頓時間來制定回收計劃,然后把決定回收的分區(qū)的存活對象復制到空的分區(qū)中,再清理掉整個舊的分區(qū)的全部空間。這里的操作涉及存活對象的移動,會暫停用戶線程,由多條收集器線程并行完成。
常用的 JVM 調優(yōu)的參數(shù)都有哪些?
-Xms1g:初始化堆大小為 1g;-Xmx1g:堆最大內存為 1g;-XX:NewRatio=4:設置新生代和老年代的內存比例為 1:4;-XX:SurvivorRatio=8:設置Eden和Survivor比例為 8:2;-XX:+PrintGC:打印 GC 信息;-XX:+PrintGCDetails:打印 GC 詳細信息;–XX:+UseParNewGC:指定使用ParNew+Serial Old垃圾回收器;-XX:+UseParallelOldGC:指定使用ParNew+ParNew Old垃圾回收器;-XX:+UseConcMarkSweepGC:指定使用CMS+Serial Old垃圾回收器。
JVM調優(yōu)工具有哪些?
jps:列出本機所有 Java 進程的進程號。
常用參數(shù)如下:
-m輸出main方法的參數(shù)-l輸出完全的包名和應用主類名-v輸出JVM參數(shù)
jps?-lvm
//output
//4124?com.zzx.Application?-javaagent:E:\IDEA2019\lib\idea_rt.jar=10291:E:\IDEA2019\bin?-Dfile.encoding=UTF-8
jstack:查看某個 Java 進程內的線程堆棧信息。使用參數(shù)-l可以打印額外的鎖信息,發(fā)生死鎖時可以使用jstack -l pid觀察鎖持有情況。
jstack?-l?4124?|?more
輸出結果如下:
"http-nio-8001-exec-10"?#40?daemon?prio=5?os_prio=0?tid=0x000000002542f000?nid=0x4028?waiting?on?condition?[0x000000002cc9e000]
???java.lang.Thread.State:?WAITING?(parking)
????????at?sun.misc.Unsafe.park(Native?Method)
????????-?parking?to?wait?for??<0x000000077420d7e8>?(a?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
????????at?java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
????????at?java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
????????at?java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
????????at?org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
????????at?org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
????????at?java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
????????at?java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
????????at?java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
????????at?org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
????????at?java.lang.Thread.run(Thread.java:748)
???Locked?ownable?synchronizers:
????????-?None
WAITING (parking)指線程處于掛起中,在等待某個條件發(fā)生,來把自己喚醒。
jstat:用于查看虛擬機各種運行狀態(tài)信息(類裝載、內存、垃圾收集等運行數(shù)據(jù))。使用參數(shù)-gcuitl可以查看垃圾回收的統(tǒng)計信息。
jstat?-gcutil?4124
??S0?????S1?????E??????O??????M?????CCS????YGC?????YGCT????FGC????FGCT?????GCT
??0.00???0.00??67.21??19.20??96.36??94.96?????10????0.084?????3????0.191????0.275
參數(shù)說明:
S0: Survivor0區(qū)當前使用比例S1: Survivor1區(qū)當前使用比例E: Eden區(qū)使用比例O:老年代使用比例 M:元數(shù)據(jù)區(qū)使用比例 CCS:壓縮使用比例 YGC:年輕代垃圾回收次數(shù) FGC:老年代垃圾回收次數(shù) FGCT:老年代垃圾回收消耗時間 GCT:垃圾回收消耗總時間
jmap:查看堆內存快照。通過jmap命令可以獲得運行中的堆內存的快照,從而可以對堆內存進行離線分析。
查詢進程4124的堆內存快照,輸出結果如下:
>jmap?-heap?4124
Attaching?to?process?ID?4124,?please?wait...
Debugger?attached?successfully.
Server?compiler?detected.
JVM?version?is?25.221-b11
using?thread-local?object?allocation.
Parallel?GC?with?6?thread(s)
Heap?Configuration:
???MinHeapFreeRatio?????????=?0
???MaxHeapFreeRatio?????????=?100
???MaxHeapSize??????????????=?4238344192?(4042.0MB)
???NewSize??????????????????=?88604672?(84.5MB)
???MaxNewSize???????????????=?1412431872?(1347.0MB)
???OldSize??????????????????=?177733632?(169.5MB)
???NewRatio?????????????????=?2
???SurvivorRatio????????????=?8
???MetaspaceSize????????????=?21807104?(20.796875MB)
???CompressedClassSpaceSize?=?1073741824?(1024.0MB)
???MaxMetaspaceSize?????????=?17592186044415?MB
???G1HeapRegionSize?????????=?0?(0.0MB)
Heap?Usage:
PS?Young?Generation
Eden?Space:
???capacity?=?327155712?(312.0MB)
???used?????=?223702392?(213.33922576904297MB)
???free?????=?103453320?(98.66077423095703MB)
???68.37795697725736%?used
From?Space:
???capacity?=?21495808?(20.5MB)
???used?????=?0?(0.0MB)
???free?????=?21495808?(20.5MB)
???0.0%?used
To?Space:
???capacity?=?23068672?(22.0MB)
???used?????=?0?(0.0MB)
???free?????=?23068672?(22.0MB)
???0.0%?used
PS?Old?Generation
???capacity?=?217579520?(207.5MB)
???used?????=?41781472?(39.845916748046875MB)
???free?????=?175798048?(167.65408325195312MB)
???19.20285144484187%?used
27776?interned?Strings?occupying?3262336?bytes.
main方法的執(zhí)行過程?
以下是示例代碼:
public?class?Application?{
????public?static?void?main(String[]?args)?{
????????Person?p?=?new?Person("大彬");
????????p.getName();
????}
}
class?Person?{
????public?String?name;
????public?Person(String?name)?{
????????this.name?=?name;
????}
????public?String?getName()?{
????????return?this.name;
????}
}
執(zhí)行main方法的過程如下:
編譯 Application.java后得到Application.class后,執(zhí)行這個class文件,系統(tǒng)會啟動一個JVM進程,從類路徑中找到一個名為Application.class的二進制文件,將Application類信息加載到運行時數(shù)據(jù)區(qū)的方法區(qū)內,這個過程叫做類的加載。JVM 找到 Application的主程序入口,執(zhí)行main方法。main方法的第一條語句為Person p = new Person("大彬"),就是讓 JVM 創(chuàng)建一個Person對象,但是這個時候方法區(qū)中是沒有Person類的信息的,所以 JVM 馬上加載Person類,把Person類的信息放到方法區(qū)中。加載完 Person類后,JVM 在堆中分配內存給Person對象,然后調用構造函數(shù)初始化Person對象,這個Person對象持有指向方法區(qū)中的 Person 類的類型信息的引用。執(zhí)行 p.getName()時,JVM 根據(jù) p 的引用找到 p 所指向的對象,然后根據(jù)此對象持有的引用定位到方法區(qū)中Person類的類型信息的方法表,獲得getName()的字節(jié)碼地址。執(zhí)行 getName()方法。
對象的創(chuàng)建過程?
類加載檢查:當虛擬機遇到一條 new指令時,首先檢查是否能在常量池中定位到這個類的符號引用,并且檢查這個符號引用代表的類是否已被加載過、解析和初始化過。如果沒有,那先執(zhí)行類加載。分配內存:在類加載檢查通過后,接下來虛擬機將為對象實例分配內存。 初始化。分配到的內存空間都初始化為零值,通過這個操作保證了對象的字段可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應的零值。 設置對象頭。 Hotspot虛擬機的對象頭包括:存儲對象自身的運行時數(shù)據(jù)(哈希碼、分代年齡、鎖標志等等)、類型指針和數(shù)據(jù)長度(數(shù)組對象才有),類型指針就是對象指向它的類信息的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。按照 Java代碼進行初始化。
參考資料
周志明. 深入理解 Java 虛擬機 [M]. 機械工業(yè)出版社
