《深刻理解JAVA虛擬機》筆記

第1章 走進Java
1.1 Java技術體系
Java技術體系包括:Java程序設計語言、各類硬件平臺上的Java虛擬機、Class文件格式、Java API類庫、第三方Java類庫。
JDK:Java Development Kit,用於支持Java程序開發的最小環境,包括:Java程序設計語言、Java虛擬機、Java API類庫。
JRE:Java Runtime Environment,支持Java程序運行的標準環境,包括:JAVA SE API子集、Java虛擬機。
 
1.2 Java發展史
1991年4月 Oak橡樹,java語言的前身。
1995年5月 Oak更名爲Java,第一次提出「Write Once,Run Anywhere」的口號。
1996年1月23日 JDK 1.0發佈,提供了一個純解釋執行的Java虛擬機實現(Sun Classic VM)。
1999年4月 HotSpot虛擬機發布,爲Sun公司收購的一家小公司開發,成爲JDK 1.3及之後版本的默認虛擬機。
 
1.3 Java虛擬機發展史
Dalvik VM:Android平臺的核心組成部分,它並非一個虛擬機,沒有遵循Java虛擬機規範,不能直接執行Java的Class文件,使用的是寄存器架構而不是JVM中常見的棧架構。它執行的dex文件能夠經過class文件轉換而來。
 
1.4 展望Java技術的將來
模塊化、混合語言、多核並行、進一步豐富語法、64位虛擬機。
 
 
第2章 Java內存區域與內存溢出異常
2.1 JVM運行時數據區
(1)程序計數器
線程私有的一塊較小的內存空間,能夠看作是當前線程所執行的字節碼的行號指示器。
(2)虛擬機棧
VM Stack是線程私有的,生命週期與線程相同。描述的是java方法執行的內存模型:每一個方法執行時會建立一個棧幀(Stack Frame),用於存儲局部變量表、操做數棧、動態連接、方法出口等。
JAVA內存粗糙劃分爲堆內存(Heap)和棧內存(Stack)。此棧內存即虛擬機棧,當棧深度不足,會拋出StackOverflowError異常。
局部變量表所需的內存空間大小在編譯期完成分配並肯定,方法運行期間不會改變局部變量表的大小。
(3)本地方法棧
虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的Native方法服務。
Sun HotSpot虛擬機會將本地方法棧和虛擬機棧合二爲一。
(4)Java堆
Java Heap是JVM所管理的內存中最大一塊,被全部線程共享,惟一目的就是存放對象實例。
java堆是垃圾收集器管理的主要區域,也叫GC堆。主要分爲三種空間:新生代(Eden、From Survivor、To Survivor)和老年代,以及多個線程私有的分配緩衝區TLAB。
(5)方法區
Method Area,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
HotSpot虛擬機爲了管理這部份內存,將GC分代收集擴展至方法區,主要針對常量池的回收和對類型的卸載,並稱爲永久代(Permanent Generation),而其餘虛擬機沒有永久代概念。
(6)運行時常量池
Runtime Constant Pool,是方法區的一部分。用於存放編譯期生成的各類字面量和符號引用。也能夠在運行期將新的常量放入池中,如String類的intern()方法。
(7)直接內存
Direct Memory,不是虛擬機運行時數據區的一部分,也不是JVM規範定義的內存區域。JDK 1.4引入NIO 類,是一種基於通道Channel和緩衝區Buffer的IO方式,它可使用Native函數直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣的好處是避免了在Java堆和Native堆中來回複製數據,從而顯著提升性能。
本地直接內存的分配不會受到Java堆大小的限制。
 
2.2 HotSpot虛擬機對象探祕
(1)對象的建立
虛擬機遇到一條new指令時,首先將檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用所表明的類是否已被加載、解析和初始化過。若是沒有,那必須先執行相應的類加載過程。
在類加載檢查經過後,接下來虛擬機將爲新生對象分配內存。有以下兩種分配方式:
①指針碰撞
僅當Java堆內存是規整的,已有和空閒內存各在一邊,經過將分界點指針向空閒空間挪動來分配。如Serial、ParNew等帶Compact壓縮過程的收集器採用此方式。
②空閒列表
當Java堆內存不是規整的,如CMS等基於Mark-Sweep算法的收集器採用此方式。
 
另外,在併發環境下爲了保證線程安全的分配,有兩種方法:
①經過循環CAS來保證分配動做的原子性。
②在TLAB(本地線程分配緩衝)中分配,線程隔離,當TLAB空間不足時,才須要方法1的同步鎖定。
 
內存分配完成後,虛擬機須要將分配到的內存空間都初始化爲零值(不包括對象頭)。接下來,JVM對對象頭信息進行設置,如:類元數據信息、對象哈希碼、對象GC分代年齡等。
上述工做都完成後,才調用對象的init方法開始初始化。
 
(2)對象的內存佈局
HotSpot虛擬機中,對象在內存的存儲佈局分爲3塊區域:對象頭(Header)、實例數據(Instance Data)、對齊填充(Padding)。
其中對象頭包含2~3個字,第一個字爲Mark Word,存儲對象自身的運行時數據,如哈希碼,GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID等。第二個字爲類型元數據的指針。第三個字爲數組長度(當對象是數組才存在)。
對齊填充:虛擬機要求對象起始地址必須是8字節的倍數,所以當實例數據部分沒有對齊時,就須要對齊填充。
 
(3)對象的訪問定位
java程序須要經過棧上的reference類型數據去訪問堆上的具體對象,目前有兩種訪問方式:
①使用句柄訪問
java堆中會劃分出一塊內存來做爲句柄池,reference中存儲的就是句柄地址,句柄包含對象實例數據和類型數據的地址信息。
最大優勢:reference存儲穩定的句柄地址,GC移動對象時只需改變句柄內容,不須要修改reference。
②使用直接指針訪問
reference中存儲的直接就是對象地址,而對象實例數據在對象頭第二個字中包含對象類型數據的地址信息。HotSpot虛擬機對象定位採用直接指針訪問。
最大優勢:速度更快,節省一次指針定位的開銷。
 
2.3 虛擬機OutOfMemoryError異常
①-Xms 設置Java堆得最小值;-Xmx 設置Java堆最大值。
②-XX:+HeapDumpOnOutOfMemoryError 讓虛擬機在OOM時Dump出當前的內存轉儲快照。
③-Xss 設置虛擬機棧大小。
④-XX:PermSize 設置方法區大小;-XX:MaxPermSize 設置方法區最大容量。在JDK1.8中已無效,經過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來代替。
 
2.4 String.intern()
String.intern()是一個Native方法,做用是:若是字符串常量池中已經包含一個等於此String對象的字符串,則返回表明池中這個字符串的String對象;不然,將此String對象包含的字符串添加到常量池中,而且返回此String對象的引用。
當JDK1.6時,由於常量池分配在永久代中,intern()會把首次遇到的字符串實例複製到永久代,返回永久代中這個字符串實例的引用。
當JDK1.7時,由於常量池沒有分配在永久代,而是移到java堆。intern()不會再複製,只是在常量池中記錄首次出現的實例引用。
當JDK1.8時,永久代因GC回收效率過低且複雜度高、永久代字符串容易溢出等緣由被徹底移除,採用元空間(Metaspace)區域來代替。
常見試題解答:
Q:下列程序的輸出結果:
String s1 = 「abc」;
String s2 = 「abc」;
System.out.println(s1 == s2);
A:true,均指向常量池中對象。
 
Q:下列程序的輸出結果:
String s1 = new String(「abc」);
String s2 = new String(「abc」);
System.out.println(s1 == s2);
A:false,兩個引用指向堆中的不一樣對象。
 
Q:下列程序的輸出結果:
String s1 = 「abc」;
String s2 = 「a」;
String s3 = 「bc」;
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:false,由於s2+s3其實是使用StringBuilder.append來完成,會生成不一樣的對象。
 
Q:下列程序的輸出結果:
String s1 = 「abc」;
final String s2 = 「a」;
final String s3 = 「bc」;
String s4 = s2 + s3;
System.out.println(s1 == s4);
A:true,由於final變量在編譯後會直接替換成對應的值,因此實際上等於s4=」a」+」bc」,而這種狀況下,編譯器會直接合併爲s4=」abc」,因此最終s1==s4。
 
Q:下列程序的輸出結果:
String s = new String(「abc」);
String s1 = 「abc」;
String s2 = new String(「abc」);
System.out.println(s == s1.intern());
System.out.println(s == s2.intern());
System.out.println(s1 == s2.intern());
A:false,false,true。
 
 
第3章 垃圾收集器與內存分配策略
3.1 對象已死嗎
GC垃圾收集以前,有兩種方法判斷對象是否「死去」(即不可能再被任何途徑使用的對象):
①引用計數法
優勢:簡單,判斷效率高。
缺點:很難解決對象之間相互循環引用問題,主流JVM全都沒有采用此方法。
②可達性分析算法
基本思想:當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。
可做爲起始點GC Roots的對象有四種:虛擬機棧(棧幀中的本地變量表)中引用的對象、方法區中類靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中JNI(即Native方法)引用的對象。
 
java將引用按強度依次遞減,分爲四種:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。
強引用:用new建立,只要存在,GC就不會回收。
軟引用:描述一些還有用但並不是必需的對象。在即將OOM以前,會對軟引用對象第二次回收,若是尚未足夠內存才拋出異常。
弱引用:描述非必需對象。只能生存到下次GC以前。
虛引用:徹底不會對生存時間有影響,也沒法經過虛引用獲取對象。惟一目的:對象被GC時收到一個系統通知。
 
即便在可達性分析算法中不可達對象,也並非「非死不可」,要宣告死亡,至少要通過兩次標記過程:
①第一次標記
若是對象不可達,對象會被第一次標記而且進行一次篩選,篩選條件是此對象有沒有必要執行finalize()方法。當已執行過或方法沒覆蓋,則視爲沒有必要執行。
②第二次標記
當對象有必要執行finalize方法時,會將對象放入F-Queue隊列,由低優先級的Finalizer線程去執行但不會等到方法結束(防止方法內部死循環),稍後GC會對隊列中的對象進行第二次標記,若是對象在finalize方法中與引用鏈中任何對象創建關聯,則成功解救本身。不然被真正回收。
由於finalize方法不肯定是否執行完成,不肯定性大,故不建議用其來作清理工做,應該用try-finally語句塊代替。
 
方法區類回收,類須要同時知足三個條件才能算是「無用的類」:
①該類全部的實例都已經被回收。
②加載該類的ClassLoader已經被回收。
③該類對應的java.lang.Class對象沒有在任何地方被引用,沒法經過反射訪問該類。
 
3.2 垃圾收集算法
(1)標記-清除算法
Mark-Sweep算法是最基礎的GC算法。後續的收集算法都是基於該算法思路並對其不足進行改進。
缺點:①標記和清除兩個過程效率都不高;②清除後會產生大量不連續的內存碎片。內存碎片太多可能致使當須要分配較大對象時,沒法找到足夠連續內存而不得不提早觸發一次GC。
 
(2)複製算法
將內存按容量劃分爲大小相等的兩塊,每次只使用其中一塊,當這塊內存用完了,就將還存活的對象複製到另一塊上,而後再把已使用的內存空間一次清理掉。
優勢:簡單高效。
缺點:內存只用一半,浪費嚴重。
 
如今的商業虛擬機都採用複製算法來回收新生代。由於新生代中對象98%都是「朝生夕死」,故沒必要按1:1平份內存,而將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,當回收時,將Eden和From Survivor中還存活的對象一次性複製到To Survivor中,默認比例是8:1:1,只有10%的內存被浪費。當To Survivor空間不夠用時,須要額外的空間(老年代)進行分配擔保。
 
(3)標記-整理算法
複製算法在對象存活率較高時須要進行較多的複製操做,效率將會變低,因此老年代不能直接採用複製算法。
Mark-Compact算法是根據老年代特色提出的,標記階段與標記-清除算法相同,整理階段不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
 
(4)分代收集算法
當前商業虛擬機垃圾收集都採用「分代收集」算法。根據對象存活週期的不一樣將內存劃分爲幾塊。通常講Java堆分爲新生代和老年代,新生代用複製算法,老年代用「標記-清理」或「標記-整理」算法。
 
3.3 HotSpot算法實現
枚舉根節點:可達性分析對執行時間的敏感還體如今GC停頓上,由於這項分析工做必須在一個能確保一致性的快照中進行,這點致使GC進行時必須停頓全部Java執行線程(Stop The World),即便在號稱幾乎不會停頓的CMS收集器中,枚舉CG Roots根節點時也是必需要停頓的。
在OopMap數據結構協助下,HotSpot能夠快速準確的完成CG Roots枚舉,由於類加載完成時,HotSpot就把對象內什麼偏移量上是什麼類型的數據都計算出來,另外JIT編譯時也會在特定位置記錄棧和寄存器中哪些位置是引用。
 
安全點:Safepoint,即程序執行時並不是在全部地方都能停頓下來開始GC,只有在到達安全點時才能暫停。
當GC發生時如何讓全部線程都跑到最近的安全點上再停頓下來?有以下兩種方案:
①搶先式中斷
不須要線程執行代碼主動配合,GC發生時,首先把全部線程所有中斷,若是發現有線程中斷的地方不在安全點上,就恢復該線程,讓它跑到安全點上。
如今幾乎沒有虛擬機採用此方法。
②主動式中斷
GC發生時,簡單設置一個標誌,各個線程執行到安全點時主動去輪詢這個標誌,發現中斷標誌爲真就本身中斷掛起。
 
安全區域:Safe Region,時指在一段代碼片斷之中,引用關係不會發生變化。在這個區域中的任意地方開始GC都是安全的。
由於線程處於Sleep狀態或者Blocked狀態時,線程沒法響應JVM的中斷請求來走到安全的地方去中斷掛起。此時就須要安全區域來解決。
 
3.4 垃圾收集器
收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現。JVM規範對垃圾收集器如何實現沒有任何規定。目前尚未最好或萬能的收集器。
上面有7中收集器,分爲兩塊,上面爲新生代收集器,下面是老年代收集器。若是兩個收集器之間存在連線,就說明它們能夠搭配使用。
(1)Serial(串行GC)收集器
Serial收集器是一個新生代收集器,單線程執行,使用複製算法。它在進行垃圾收集時,它不只只會使用一個CPU或者一條收集線程去完成垃圾收集做,並且必須暫停其餘全部的工做線程(用戶線程),直到它收集完成。
是Jvm client模式下默認的新生代收集器。對於限定單個CPU的環境來講,簡單高效,Serial收集器因爲沒有線程交互的開銷,專心作垃圾收集天然能夠得到最高的單線程收集效率,所以是運行在Client模式下的虛擬機的不錯選擇(好比桌面應用場景)。
Serial/Serial Old收集器運行示意圖(表示Serial和Serial Old搭配使用):
(2)ParNew(並行GC)收集器
ParNew收集器其實就是serial收集器的多線程版本,使用複製算法。除了使用多條線程進行垃圾收集以外,其他行爲與Serial收集器同樣。
它是運行在Service模式下虛擬機中首選的新生代收集器,其中一個與性能無關的緣由就是除了Serial收集器外,目前只有ParNew收集器能與CMS收集器配合工做。
ParNew收集器在單CPU環境中絕對沒有Serial的效果好,因爲存在線程交互的開銷,該收集器在超線程技術實現的雙CPU中都不能必定超過Serial收集器。默認開啓的垃圾收集器線程數就是CPU數量,可經過-XX:parallelGCThreads參數來限制收集器線程數。
ParNew/Serial Old收集器運行示意圖(表示ParNew和Serial Old搭配使用):
(3)Parallel Scavenge(並行回收GC)收集器
Parallel Scavenge收集器也是一個新生代收集器,它也是使用複製算法的收集器,又是並行多線程收集器。parallel Scavenge收集器的特色是它的關注點與其餘收集器不一樣,CMS等收集器的關注點是儘量地縮短垃圾收集時用戶線程的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程序運行時間/(程序運行時間 + 垃圾收集時間),虛擬機總共運行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。
短停頓時間適合和用戶交互的程序,體驗好。高吞吐量適合高效利用CPU,主要用於後臺運算不須要太多交互。
提供了兩個參數來精確控制吞吐量:1.最大垃圾收集器停頓時間(-XX:MaxGCPauseMillis 大於0的毫秒數,停頓時間小了就要犧牲相應的吞吐量和新生代空間),2.設置吞吐量大小(-XX:GCTimeRatio 大於0小於100的整數,默認99,也就是容許最大1%的垃圾回收時間)。
還有一個參數表示自適應調節策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)。就不用手動設置新生代大小(-Xmn)、Eden和Survivor區的比例(-XX:SurvivorRatio)此生老年代對象大小(-XX:PretenureSizeThreshold),會根據當前系統的運行狀況手機監控信息,動態調整停頓時間和吞吐量大小。也是其與PreNew收集器的一個重要區別,也是其沒法與CMS收集器搭配使用的緣由(CMS收集器儘量地縮短垃圾收集時用戶線程的停頓時間,以提高交互體驗)。
Parallel Scavenge/Parallel Old收集器運行示意圖(表示Parallel Scavenge和Parallel Old搭配使用):
(4)Serial Old(串行GC)收集器
Serial Old是Serial收集器的老年代版本,它一樣使用一個單線程執行收集,使用「標記-整理」算法。主要使用在Client模式下的虛擬機。
若是在Service模式下使用:1.一種是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用,由於那時尚未Parallel Old老年代收集器搭配;2.另外一種就是做爲CMS收集器的後備預案,在併發收集發生Concurrent Model Failure時使用。
(5)Parallel Old(並行GC)收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法,JDK1.6才提供。
因爲以前有一個Parallel Scavenge新生代收集器,,可是卻無老年代收集器與之完美結合,只能採用Serial Old老年代收集器,可是因爲Serial Old收集器在服務端應用性能上低下(畢竟單線程,多CPU浪費了),其吞吐量反而不必定有PreNew+CMS組合。
Parallel Scavenge/Parallel Old收集器運行示意圖(表示Parallel Scavenge和Parallel Old搭配使用):
(6)CMS(併發GC)收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是HotSpot虛擬機中的一款真正意義上的併發收集器,第一次實現了讓垃圾回收線程和用戶線程(基本上)同時工做。用CMS收集老年代的時候,新生代只能選擇Serial或者ParNew收集器。
CMS收集器是基於「標記-清除」算法實現的,整個收集過程大體分爲4個步驟:
①.初始標記(CMS initial mark)
②.併發標記(CMS concurrenr mark)
③.從新標記(CMS remark)
④.併發清除(CMS concurrent sweep)
其中初始標記、從新標記這兩個步驟任然須要停頓其餘用戶線程(Stop The World)初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會斷定對象是否存活。而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。
因爲整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,因此總體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
CMS收集器運行示意圖:
CMS收集器的優勢:併發收集、低停頓,可是CMS還遠遠達不到完美,其主要有三個顯著缺點:
1.CMS收集器對CPU資源很是敏感。在併發(併發標記、併發清除)階段,雖然不會致使用戶線程停頓,可是會佔用CPU資源而致使應用程序變慢,總吞吐量降低。CMS默認啓動的回收線程數是:(CPU數量+3) / 4。收集器線程所佔用的CPU數量爲:(CPU+3)/4=0.25+3/(4*CPU)。所以這時垃圾收集器始終不會佔用少於25%的CPU,所以當進行併發階段時,雖然用戶線程能夠跑,可是很緩慢,特別是雙核CPU的時候,已經佔用了5/8的CPU,吞吐量會很低。爲了解決這種狀況,產生了「增量式併發收集器」(Incremental Concurrent Mark Sweep/i-CMS)。就是採用搶佔方式來模擬多任務機制,就是在併發(併發標記、併發清除)階段,讓GC線程、用戶線程交替執行,儘可能減小GC線程獨佔CPU,這樣垃圾收集過程更長,可是對用戶程序影響小一些。實際上i-CMS效果很通常,目前已經被聲明爲「deprecated」。
2.CMS收集器沒法處理浮動垃圾,可能出現「Concurrent Mode Failure「,失敗後而致使另外一次Full GC的產生。因爲CMS併發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱爲「浮動垃圾」。也是因爲在垃圾收集階段用戶線程還須要運行,即須要預留足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,須要預留一部份內存空間提供併發收集時的程序運做使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也能夠經過參數-XX:CMSInitiatingOccupancyFraction的值來提升觸發百分比,以下降內存回收次數提升性能。JDK1.6中,CMS收集器的啓動閾值已經提高到92%。要是CMS運行期間預留的內存沒法知足程序其餘線程須要,就會出現「Concurrent Mode Failure」失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集,這樣停頓時間就很長了。因此說參數-XX:CMSInitiatingOccupancyFraction設置的太高將會很容易致使「Concurrent Mode Failure」失敗,性能反而下降。
3.最後一個缺點,CMS是基於「標記-清除」算法實現的收集器,使用「標記-清除」算法收集後,會產生大量碎片。空間碎片太多時,將會給對象分配帶來不少麻煩,好比說大對象,內存空間找不到連續的空間來分配不得不提早觸發一次Full GC。爲了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full GC以後增長一個內存碎片的合併整理過程,可是內存整理過程是沒法併發的,所以解決了空間碎片問題,卻使停頓時間變長。還可經過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full GC以後,跟着來一次碎片整理過程(默認值是0,表示每次進入Full GC時都進行碎片整理)。
 
(7)G1收集器
G1(Garbage First)收集器是JDK1.7提供的一個新的面向服務端應用的垃圾收集器,其目標就是替換掉JDK1.5發佈的CMS收集器。其優勢有:
1.併發與並行:G1能充分利用多CPU、多核環境下的硬件優點,使用多個CPU(CPU或CPU核心)來縮短停頓(Stop The World)時間。
2.分代收集:G1不須要與其餘收集器配合就能獨立管理整個GC堆,但他可以採用不一樣方式去處理新建對象和已經存活了一段時間、熬過屢次GC的老年代對象以獲取更好收集效果。
3.空間整合:從總體來看是基於「標記-整理」算法實現,從局部(兩個Region之間)來看是基於「複製」算法實現的,可是都意味着G1運行期間不會產生內存碎片空間,更健康,遇到大對象時,不會由於沒有連續空間而進行下一次GC,甚至一次Full GC。
4.可預測的停頓:下降停頓是G1和CMS共同關注點,但G1除了追求低停頓,還能創建可預測的停頓模型,能夠明確地指定在一個長度爲M的時間片內,消耗在垃圾收集的時間不超過N毫秒
5.跨代特性:以前的收集器進行收集的範圍都是整個新生代或老年代,而G1擴展到整個Java堆(包括新生代,老年代)。
 
那麼是怎麼實現的呢?
1.如何實現新生代和老年代全範圍收集:其實它的Java堆佈局就不一樣於其他收集器,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),仍然保留新生代和老年代的概念,但是不是物理隔離的,都是一部分Region(不須要連續)的集合。
2.如何創建可預測的停頓時間模型:是由於有了獨立區域Region的存在,就避免在Java堆中進行全區域的垃圾收集,G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收能夠得到的空間大小和回收所須要的時間的經驗值),後臺維護一個優先隊列,根據每次容許的收集時間,優先回收價值最大的Region(Garbage-First理念)。所以使用Region劃份內存空間以及有優先級的區域回收方式,保證了有限時間得到儘量高的收集效率。
3.如何保證垃圾回收真的在Region區域進行而不會擴散到全局:因爲Region並非孤立的,一個Region的對象能夠被整個Java堆的任意其他Region的對象所引用,在作可達性斷定肯定對象是否存活時,仍然會關聯到Java堆的任意對象,G1中這種狀況特別明顯。而之前在別的分代收集裏面,新生代規模要比老年代小許多,新生代收集也頻繁得多,也會涉及到掃描新生代時也會掃描老年代的狀況,相反亦然。解決:G1收集器Region之間的對象引用以及新生代和老年代之間的對象引用,虛擬機都是使用Remembered Set來避免全堆掃描。G1中每一個Region都有一個與之對應的Remembered Set,虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region之中(分代的例子中就檢查是否老年代對象引用了新生代的對象),若是是則經過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中,當進行內存回收時,在GC根節點的枚舉範圍中加入Remembered Set便可避免全堆掃描。
忽略Remembered Set的維護,G1的運行步驟可簡單描述爲:
①.初始標記(Initial Marking)
②.併發標記(Concurrenr Marking)
③.最終標記(Final Marking)
④.篩選回收(Live Data Counting And Evacution)
 
1.初始標記:初始標記僅僅標記GC Roots能直接關聯到的對象,而且修改TAMS(Next Top at Mark Start)的值,讓下一階段用戶程序併發運行時,能在正確可用的Region中建立新的對象。這階段須要停頓線程,不可並行執行,可是時間很短。
2.併發標記:此階段是從GC Roots開始對堆中對象進行可達性分析,找出存活對象,此階段時間較長可與用戶程序併發執行。
3.最終標記:此階段是爲了修正在併發標記期間由於用戶線程繼續運行而致使標記產生變更的那一份標記記錄,虛擬機將這段時間對象變化記錄在線程Remembered Set Logs裏面,最終標記階段須要把Remembered Set Logs的數據合併到Remembered Set中,這段時間須要停頓線程,可是可並行執行。
4.篩選回收:對各個Region的回收價值和成本進行排序,根據用戶指望的GC停頓時間來制定回收計劃。
 
若是現有的垃圾收集器沒有出現任何問題,沒有任何理由去選擇G1,若是應用追求低停頓,G1可選擇,若是追求吞吐量,和Parallel Scavenge/Parallel Old組合相比,G1並無特別的優點。
 
垃圾收集器參數總結
-XX:+<option> 啓用選項
-XX:-<option> 不啓用選項
-XX:<option>=<number>
-XX:<option>=<string>
 
Client、Server模式默認GC:
 
3.5 理解GC日誌
「[Full GC」 只要帶有Full,則說明此次GC是發生了Stop-The-World的。
 
3.6 內存分配與回收策略
新生代GC:Minor GC,朝生夕滅,很是頻繁,回收速度較快。
老年代GC:Major GC、Full GC,常常會伴隨至少一次Minor GC,速度比Minor GC慢10倍以上。
相關規則以下:
①對象優先在Eden分配,當Eden沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。
②大對象(如字符串、數組)直接進入老年代。最怕遇到一羣朝生夕滅的短命大對象。大小閾值由JVM參數控制。
③長期存活的對象將進入老年代。對象GC年齡閾值由JVM參數控制。
④動態對象年齡判斷。不是必須超過年齡閾值才能進入老年代,若是在Survivor空間中相同年齡全部對象大小總和大於Survivor空間一半,年齡大於等於該年齡的對象就能夠直接進入老年代。
⑤只要老年代的連續空間小於新生代對象總大小或者歷次晉升的平均大小,就會進行Full GC,不然只會進行Minor GC。
 
 
第4章 虛擬機性能監控與故障處理工具
4.1 JDK的命令行工具
(1)JPS
jps主要用來輸出JVM中運行的進程狀態信息。語法格式以下: jps [options] [hostid] 。
命令行參數選項說明以下:
-q 不輸出類名、Jar名和傳入main方法的參數
-m 輸出傳入main方法的參數
-l 輸出main類或Jar的全限名
-v 輸出傳入JVM的參數
 
(2)jstack
jstack主要用來查看某個Java進程內的線程堆棧信息。語法格式以下:
jstack [option] pid
jstack [option] executable core
jstack [option] [server-id@]remote-hostname-or-ip
命令行參數選項說明以下:
-l long listings,會打印出額外的鎖信息,在發生死鎖時能夠用jstack -l pid來觀察鎖持有狀況
-m mixed mode,不只會輸出Java堆棧信息,還會輸出C/C++堆棧信息(好比Native方法)
 
(3)jmap(Memory Map)和jhat(Java Heap Analysis Tool)
jmap用來查看堆內存使用情況,通常結合jhat使用。jmap語法格式以下:
jmap [option] pid
jmap [option] executable core
jmap [option] [server-id@]remote-hostname-or-ip
若是運行在64位JVM上,可能須要指定-J-d64命令選項參數。
①jmap -permstat pid
打印進程的類加載器和類加載器加載的持久代對象信息,輸出:類加載器名稱、對象是否存活(不可靠)、對象地址、父類加載器、已加載的類大小等信息。
②jmap -heap pid
查看進程堆內存使用狀況,包括使用的GC算法、堆配置參數和各代中堆內存使用狀況。
③jmap -histo[:live] pid
查看堆內存中的對象數目、大小統計直方圖,若是帶上live則只統計活對象。
④jmap -dump:format=b,file=dumpFileName pid
用jmap把進程內存使用狀況dump到文件中,再用jhat、MAT、VisualVM等工具查看。
 
(4)jstat(JVM統計監測工具)
語法格式以下:jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ] 。
vmid是Java虛擬機ID,在Linux/Unix系統上通常就是進程ID。interval是採樣時間間隔。count是採樣數目。
①jstat -gc 21711 250 4
輸出的是GC信息,採樣時間間隔爲250ms,採樣數爲4。
 
(5)jinfo
java配置信息工具,實時查看和調整虛擬機各項參數。
 
(6)hprof(Heap/CPU Profiling Tool)
hprof可以展示CPU使用率,統計堆內存使用狀況。語法格式以下:
java -agentlib:hprof[=options] ToBeProfiledClass
java -Xrunprof[:options] ToBeProfiledClass
javac -J-agentlib:hprof[=options] ToBeProfiledClass
 
 
第5章 類文件結構
5.1 字節碼
Native Code:二進制本地機器碼。
Byte Code:字節碼。程序編譯後的一種與操做系統和機器指令集無關,平臺中立的存儲格式。爲了實現「一次編寫,處處運行」。
Class文件:存儲字節碼的二進制文件,Class文件包含了Java虛擬機指令集和符號表以及若干其餘輔助信息。
語言無關性:實現語言無關性的基礎是虛擬機和字節碼存儲格式。Java虛擬機不和包括Java在內的任何語言綁定,它只與「Class文件」這種特定的二進制文件格式所關聯。各類語言的編譯器把源代碼編譯成Class文件,虛擬機並不關心Class的來源是何種語言。
Java發展之初,設計者就刻意把Java規範拆分紅《Java語言規範》和《Java虛擬機規範》,以便將來更好支持其餘語言也運行於JVM之上。
 
5.2 Class類文件的結構
任何一個Class文件都對應着惟一的一個類或接口的定義信息,而類或接口並不必定都得定義在文件裏(譬如類或接口也能夠經過類加載器直接生成)。
Class文件是二進制文件,各個數據項目嚴格按照順序緊湊排列,沒有任何分隔符,數據項按大端順序(最高位字節在地址最低位)存儲。因爲沒有分隔符,Class文件中每一個字節的含義、長度、前後順序等都是嚴格限定的,不容許被改變。採用相似C語言結構體的僞結構,僞結構中只有兩種數據類型:無符號數和表。
①無符號數
屬於基本數據類型,以u一、u二、u四、u8來分別表明1個字節到8個字節的無符號數,能夠用來描述數字、索引引用、數量值或者UTF8編碼字符串。
②表
由多個無符號數或其餘表構成的有層次關係的複合數據類型,以「_info」結尾。真個Class文件本質上就是一張表。主要有cp_info(常量池),field_info(字段)、method_info(方法)、attribute_info(屬性)。
 
(1)魔數
Magic Number:每一個Class文件的頭4個字節。惟一做用是肯定這個文件是否爲一個能被虛擬機接受的Class文件。Class文件的魔數固定爲0xCAFEBABE。
(2)常量池
第一個表類型,Class文件的資源倉庫,是與其餘項目關聯最多的數據類型。常量池中每一項常量都是一個表,總共有14種常量表類型。主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic References)。
字面量:比較接近Java語言層面的常量概念,如文本字符串、final常量值等。
符號引用:屬於編譯原理概念,包含:類和接口的全限定名、字段的名稱和描述符、方法的名稱和描述符。
 
5.3 字節碼指令
JVM指令:由操做碼(Opcode,一個字節)和跟隨其後的零至多個操做數(Operands)構成。大多數指令都只有一個操做碼,不含操做數。
字節碼指令集的缺點:因爲限制操做碼長度爲一個字節,JVM操做碼總數最大256條;操做數長度不對齊,解釋執行會損失一些性能。
字節碼指令集的優勢:缺點也正是優勢,儘量得到短小精幹的編譯代碼。
操做碼指令分爲以下幾大類:
(1)加載和存儲指令
(2)運算指令
(3)類型轉換指令
(4)對象建立與訪問指令
(5)操做數棧管理指令
(6)控制轉移指令
(7)方法調用和返回指令
(8)異常處理指令
(9)同步指令
monitorenter、monitorexit指令。用於java語言的synchronized同步控制。
 
 
第6章 虛擬機類加載機制
6.1 概述
JVM類加載機制:程序運行期間,虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型。
從JSP到OSGI技術,都使用了Java語言運行期類加載的特性。Class文件不必定是磁盤文件,還多是任何形式存在的一串二進制字節流。
 
6.2 類加載的時機
類的整個生命週期包括7個階段:加載、鏈接(驗證、準備、解析)、初始化、使用、卸載。
JVM規範沒有對第一階段加載作強制約束,但對初始化階段嚴格規定有且只有5種狀況必須對類進行「初始化」,分別以下:
①遇到new、getstatic、putstatic或invokestatic這4條指令時,若是類沒有進行過初始化,則須要先觸發其初始化。
②使用java.lang.reflect包的方法對類進行反射調用時,若是類沒有進行過初始化,則須要先觸發其初始化。
③當初始化一個類時,若是父類尚未初始化,則須要先觸發父類的初始化。可是一個接口在初始化時並不要求其父接口所有都完成初始化,只有真正使用父接口時纔會初始化。
④當虛擬機啓動時,會先初始化那個用戶指定的包含main()方法的主類。
⑤當使用JDK1.7動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個句柄對應的類沒有初始化,則須要先觸發其初始化。
 
除此以外,全部引用類的方式都不會觸發初始化,稱爲被動引用。
 
6.3 類加載的過程
(1)加載
JVM在加載階段完成3件事:
①經過一個類的全限定名來獲取定義此類的二進制字節流。
②將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。
③在內存中生成一個表明此類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。
 
(2)驗證
驗證是鏈接階段的第一步,目的是爲了確保Class文件的字節流包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。驗證有4個階段的校驗動做:文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。
 
(3)準備
準備階段是正式爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中分配。這時候內存分配僅包括static類變量,不包括實例變量。
 
(4)解析
解析是虛擬機將常量池內的符號引用替換爲直接引用的過程。
符號引用:Symbolic Reference,以一組符號來表示引用的目標,符號能夠是任何形式的字面量,符號引用與虛擬機實現的內存佈局無關,引用的目標不必定已經加載到內存中。
直接引用:Direct Reference,是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用與虛擬機實現的內存佈局相關,引用目標一定已經在內存中存在。
 
(5)初始化
類初始化是類加載過程的最後一步,前面幾部徹底由虛擬機主導和控制,到了初始化階段,才真正開始執行類中定義的java程序代碼(或者說是字節碼)。
 
6.4 類加載器
類加載器:虛擬機設計團隊把類加載階段中的「經過一個類的全限定名來獲取描述此類的二進制字節流」這個動做放到Java虛擬機外部去實現,以便讓應用程序本身決定如何獲取所須要的類,實現這個動做的代碼模塊稱爲「類加載器」。
對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性,每一個類加載器,都擁有一個獨立的類命名空間。
比較兩個類是否「相等」,只有在這兩個類是由同一個類加載器加載的前提下才有意義。這裏的相等包括Class對象的equals()方法、isInstance()方法的返回結果,也包括instanceof關鍵字的對象所屬關係判斷等。
 
6.5 雙親委派模型
類加載器分爲4種:啓動類加載器、擴展類加載器、應用程序類加載器、自定義類加載器。
①啓動類加載器:Bootstrap ClassLoader,C++編寫,負責加載java_home/lib目錄或者-Xbootclasspath參數指定的路徑。
②擴展類加載器:其如下的類加載器都是Java編寫,它負責加載java_home/lib/ext目錄或者java.ext.dirs系統變量指定路徑。
③應用程序類加載器:負責加載用戶類路徑(classpath)上所指定的類庫,若沒有自定義類加載器,則其爲程序的默認類加載器。
 
雙親委派模型要求除了頂層的啓動類加載器外,其他的類加載器都應當有本身的父加載器,下層經過組合關係來複用上層父加載器。
雙親委派工做過程:若是一個類加載器收到了類加載請求,它首先不會本身去嘗試加載這個類,而是把請求委派給父類加載器去完成,每個層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到這個類)時,子加載器纔會嘗試本身加載。
雙親委派好處:Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。很好的解決了各個類加載器的基礎類的統一問題(越基礎的類由越上層的類加載器加載),從而避免混亂,保證程序穩定運做。
 
破壞雙親委派模型的幾個場景:
①JNDI服務使用線程上下文類加載器(Thread Context ClassLoader)去加載所須要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動做,全部涉及SPI代碼的加載動做都採用此方式,如:JNDI、JDBC等。
②OSGI的代碼熱替換、模塊熱部署等。OSGI實現模塊化熱部署的關鍵是它自定義的類加載器的實現。每個程序模塊(OSGI中稱爲Bundle)都有一個本身的類加載器,當須要更換Bundle時,就把Bundle連同類加載器一塊兒換掉以實現代碼的熱替換。OSGI環境下,類加載器再也不是雙親委派模型中的樹狀結構,而是更加複雜的網狀結構。
 
 
第7章 虛擬機字節碼執行引擎
7.1 概述
棧幀:Stack Frame,用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧的棧元素。棧幀存儲了方法的局部變量表、操做數棧、動態鏈接和方法返回地址等信息。每個方法從調用開始至執行完成的過程,都對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
編譯代碼時,一個棧幀須要分配多少內存已經徹底肯定,不會受運行時變量數據的影響。
(1)局部變量表
局部變量表:Local Variable Table,是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。容量以Slot槽爲最小單位。編譯後容量大小已固定。
(2)操做數棧
操做數棧:Operand Stack,棧最大深度編譯時已固定,也被寫入到字節碼中。
(3)動態鏈接
動態鏈接:Dynamic Linking,常量池中的大量符號引用,一部分在類加載階段或第一次使用時轉化爲直接引用,這種轉化稱爲靜態解析。而另一部分將在每一次運行期間轉化爲直接引用,這部分稱爲動態鏈接。
(4)方法調用
方法調用不等同於方法執行,方法調用惟一的任務就是肯定被調用方法的版本。Class文件的編譯過程不包含傳統編譯中的鏈接步驟,一切方法調用在Class文件中存儲的都是符號引用,而不是方法在實際運行時內存佈局中的入口地址(至關於直接引用),故方法調用須要在類加載期間,甚至運行期間才能肯定目標方法的直接引用。
方法調用分爲解析、靜態分配和動態分配。
解析:Resolution,調用目標在程序寫好、編譯期進行編譯時就必須肯定下來,這類方法的調用稱爲解析。java中符合「編譯期可知、運行期不可變」這個要求的方法,主要包括靜態方法和私有方法兩大類。
靜態分配:發生在編譯階段,全部依賴靜態類型來定位方法執行版本的分派動做稱爲靜態分配。如方法重載。
動態分配:發生在運行階段,和多態的重寫(Override)有密切關係。
 
7.2 基於棧的字節碼解釋執行引擎
許多java虛擬機在執行Java代碼的時候都有解釋執行(經過解釋器執行)和編譯執行(經過即時編譯器產生本地代碼執行)兩種選擇。
(1)解釋執行
JDK1.0時代,java語言被稱爲解釋執行語言還算準確。但現在主流虛擬機都包含了即時編譯器後,Class文件中的代碼被解釋執行仍是編譯執行,就只有虛擬機本身才能準確判斷了。
抽象語法樹:Abstract Syntax Tree,AST。編譯原理思路是在執行前先對程序源碼進行詞法分析和語法分析處理,把源碼轉化爲抽象語法樹。
Java語言中,Javac編譯器完成了程序代碼通過詞法分析、語法分析到抽象語法樹,再遍歷語法樹生成線性的字節碼指令流的過程。而解釋器在虛擬機內部。
(2)指令集架構
java的解釋執行所使用的指令集是基於棧的指令集架構,當熱點代碼被JIT即時編譯後,會轉化獲得基於寄存器的指令集架構。指令集架構分爲基於棧和基於寄存器兩種:
①基於棧的指令集架構
java編譯器輸出的指令流,基本上是一種基於棧的指令集架構,指令流中的指令大部分是零地址指令,他們依賴操做數棧進行工做。優勢:可移植。缺點:執行速度稍慢。
②基於寄存器的指令集架構
最典型的爲x86的二地址指令集,是主流PC機中直接支持的指令集架構。優勢:執行速度快。缺點:依賴硬件寄存器,不可移植。
 
 
第8章 早期(編譯期)優化
8.1 編譯器
編譯期:是一段不肯定的操做過程,包含了前期java文件到class文件的編譯過程,同時也包含運行期的JIT編譯或AOT編譯。
這三類編譯過程對應有三種編譯器:
①前端編譯器:javac。
②JIT編譯器:HotSpot VM的C一、C2編譯器。
③AOT編譯器:Ahead Of Time Compiler 也叫靜態提早編譯器。如:GNU Compiler for the Java(GCJ)。
 
javac這類前端編譯器對代碼的運行效率幾乎沒有任何優化措施,虛擬機設計團隊把對性能的優化集中到後端的即時編譯器中,這樣的好處是讓那些不是由javac產生的Class文件(其餘語言編譯器)也一樣可以享受到編譯器優化帶來的好處。
不少新生的Java語法特性,都是靠前端編譯器的語法糖來實現。如:泛型與類型擦除、自動裝箱、拆箱與遍歷循環等。
 
8.2 條件編譯
Java語言實現條件編譯的方法是:使用條件爲常量的if語句。
 
 
第9章 晚期(運行期)優化
9.1 HotSpot虛擬機內的即時編譯器
熱點代碼:Hot Spot Code,當JVM解釋執行時,發現某個方法或代碼塊的運行特別頻繁時,就會將這些代碼認定爲「熱點代碼」。
即時編譯器:Just In Time Compiler,JIT編譯器。爲了提升熱點代碼的執行效率,在運行時,虛擬機會把這些代碼編譯成與本地平臺相關的機器碼,並進行各類層次的優化,完成這個任務的編譯器稱爲JIT編譯器。
 
9.2 解釋器與編譯器
解釋器優勢:當程序須要迅速啓動和執行時,解釋器能夠首先發揮做用,省去編譯的時間,當即執行。
編譯器優勢:在程序運行後,隨着時間的推移,編譯器逐漸發揮做用,把愈來愈多的代碼編譯成本地代碼以後,能夠獲取更高的執行效率。
逆優化:Deoptimization,當激進優化的假設不成立,如加載了新類後類型繼承結構出現變化,就能夠經過逆優化退回到解釋狀態繼續執行。
 
9.3 C1和C2 即時編譯器
HotSpot虛擬機內置了兩個即時編譯器,分別稱爲Client Compiler(C1)和Server Compiler(C2)。目前主流的HotSport默認採用解釋器和其中一個編譯器直接配合的方式工做。虛擬機會根據自身版本和宿主機器的硬件性能自動選擇C1或C2運行模式,用戶也可使用「-client」或「-server」參數強制指定。
 
虛擬機的三種運行模式:混合模式、解釋模式、編譯模式。
混合模式:Mixed Mode,解釋器與編譯器搭配使用的方式在虛擬機中稱爲混合模式。
解釋模式:能夠用參數「-Xint」強制虛擬機運行於解釋模式。
編譯模式:也能夠用參數-Xcomp強制虛擬機運行於編譯模式(當沒法編譯時解釋器仍然要介入執行過程)。
 
9.4 分層編譯策略
分層編譯策略:爲了在程序啓動響應速度和運行效率之間達到最佳平衡,根據編譯器編譯、優化的規模與耗時,劃分出不一樣的編譯層次,分別以下:
①第0層:程序解釋執行。解釋器不開啓性能監控功能。
②第1層:也稱爲C1編譯,將字節碼編譯爲本地代碼,進行簡單、可靠的優化,若有必要將加入性能監控邏輯。
③第2層:也稱爲C2編譯,也是將字節碼編譯爲本地代碼,可是會啓用一些編譯耗時較長的優化,甚至會根據性能監控信息進行一些不可靠的激進優化。
C1編譯能獲取更高的編譯速度,而C2編譯獲取更好的編譯質量。
 
9.5 編譯對象與觸發條件
被JIT編譯器編譯的熱點代碼分爲兩類:被屢次調用的方法、被屢次執行的循環體。
熱點探測斷定方法有兩種:基於採樣點的熱點探測、基於計數器的熱點探測。HotSpot虛擬機使用基於計數器的熱點探測,它爲每一個方法準備了兩類計數器:方法調用計數器和回邊計數器。
 
9.6 編譯優化技術
編譯優化技術有近百種,最有表明性的優化技術主要爲:
①語言無關的經典優化技術:公共子表達式消除、數組範圍檢查消除。
②最重要的優化技術:方法內聯。
③最前沿的優化技術:逃逸分析。
相關文章
相關標籤/搜索