逐夢offer -- JVM篇

4. JVM

4.1 GC

1. 垃圾收集

基礎 : 可達性分析算法 GC ROOTShtml

  1. 複製算法
  2. 標記清除
  3. 標記整理
  4. 分代收集 -- 1. 新生代 ; 2.3 老年代
    注: Oop Map -- 安全點 -- 安全區

如下部份內容 來自 這個博主的文章java

1. 3種基本算法

標記清除法/標記壓縮法、複製收集算法、引用計數法程序員

這裏的 引用計數法 由於書中講解少,因此講一下:
引用計數法,它的基本原理是,在每一個對象中保存該對象的引用計數,當引用發生增減時對計數進行更新。引用計數的增減,通常發生在變量賦值、對象內容更新、函數結束(局部變量再也不被引用)等時間點。當一個對象的引用計數變爲0時,則說明它未來不會再被引用,所以能夠釋放相應的內存空間。
缺點:算法

  1. 沒法釋放循環引用的對象。
  2. 必須在引用發生增減時對引用計數作出正確的增減,而若是漏掉了某個增減的話,就會引起很難找到緣由的內存錯誤。引用數忘了增長的話,會對不恰當的對象進行釋放;而引用數忘了減小的話,對象會一直殘留在內存中,從而致使內存泄漏。
  3. 引用計數管理並不適合並行處理: 就如同 ConcurrenHashMap源碼分析 中的算法同樣,沒法在並行狀況下對數量進行準確的計算。
2. 3種進階算法
  1. 分代回收
    分代回收的目的,正是爲了在程序運行期間,將GC所消耗的時間儘可能縮短。

分代回收的基本思路,是利用了通常性程序所具有的性質,即大部分對象都會在短期內成爲垃圾,而通過必定時間依然存活的對象每每擁有較長的壽命。
HotSpot 虛擬機中,在新生代用複製算法,老年代使用標記清除/整理算法。express

問題:若是存在老生代對象對新生代對象的引用。若是隻掃描新生代區域的話,那麼從老生代對新生代的引用就不會被檢測到。
這樣一來,若是一個年輕的對象只有來自老生代對象的引用,就會被誤認爲已經「死亡」了。
所以,在分代回收中,會對對象的更新進行監視,將從老生代對新生代的引用,
記錄在一個叫作記錄集 Rset(remembered set)的表中。在執行小回收(Minor Gc)的過程當中,這個記錄集也做爲一個根來對待。segmentfault

解決方案:在老生代到新生代的引用產生的瞬間,就必須對該引用進行記錄,而負責執行這個操做的子程序,須要被嵌入到全部涉及對象更新操做的地方。
這個負責記錄引用的子程序是這樣工做的。設有兩個對象:A和B,當對A的內容進行改寫,並加入對B的引用時,
若是①A屬於老生代對象,②B屬於新生代對象,則將該引用添加到記錄集中。
這種檢查程序須要對全部涉及修改對象內容的地方進行保護,所以被稱爲寫屏障(Write barrier)數組

  1. 增量回收
    爲了維持程序的實時性,不等到GC所有完成,而是將GC操做細分紅多個部分逐一執行。這種方式被稱爲增量回收
  2. 並行回收
    並行回收的基本原理是,是在原有的程序運行的同時進行GC操做,這一點和增量回收是類似的。

不過,相對於在一個CPU上進行GC任務分割的增量回收來講,並行回收能夠利用多CPU的性能,儘量讓這些GC任務並行(同時)進行。安全

3. Card Table 數據結構

爲了支持高頻率的新生代的回收,虛擬機使用一種叫作卡表(Card Table)的數據結構.
卡表做爲一個比特位的集合,每個比特位能夠用來表示年老代的某一區域中的全部對象是否持有新生代對象的引用。
服務器

1、做用
卡表中每個位表示年老代4K的空間,
卡表記錄爲 0 的年老代區域沒有任何對象指向新生代,
卡表記錄爲 1 的區域纔有對象包含新生代引用,
所以在新生代GC時,只須要掃描卡表位爲1所在的年老代空間。使用這種方式,能夠大大加快新生代的回收速度。數據結構

2、結構
卡表是個單字節數組,每一個數組元素對應堆中的一張卡。
每次年老代對象中某個引用新生代的字段發生變化時,Hotspot VM就必須將該卡所對應的卡表元素設置爲適當的值,從而將該引用字段所在的卡標記爲髒
以下圖:

在Minor GC過程當中,垃圾收集器只會在髒卡中掃描查找年老代-新生代引用。

Hotspot VM的字節碼解釋器和JIT編譯器使用寫屏障 維護卡表。
寫屏障 (Write barrier) 是一小段將卡狀態設置爲髒的代碼。 解釋器每次執行更新引用的字節碼時,都會執行一段寫屏障,JIT編譯器在生成更新引用的代碼後,也會生成一段寫屏障。
雖然寫屏障使得應用線程增長了 -- 性能開銷,但Minor GC變快了許多,總體的垃圾收集效率也提升了許多,一般應用的吞吐量也會有所改善。

4. 評價指標

一、 吞吐量

應用系統的生命週期內,應用程序所花費的時間和系統總運行時間的比值
系統總運行時間=應用程序耗時+GC耗時
二、 垃圾回收器負載
垃圾回收器負載=GC耗時/系統總運行時間
三、 停頓時間
垃圾回收器運行時,應用程序的暫停時間
四、 垃圾回收頻率
垃圾回收器多長時間運行一次。通常而言,頻率越低越好,一般增大堆空間能夠有效下降垃圾回收發生的頻率,可是會增長回收時產生的停頓時間。
五、 反應時間
當一個對象成爲垃圾後,多長時間內,它所佔用的內存空間會被釋放掉。

2. 內存分配

1. 基礎知識

-Xms 堆大小
-Xmx 可擴展大小
-Xmn 老年代大小
-XX:SurvivorRatio Eden 區與 Survivor 區大小比例
注: surivor 區分爲 from 區與 to 區

- 在GC開始的時候,對象只會存在於Eden區和名爲「From」的Survivor區,Survivor區「To」是空的。
- 緊接着進行GC,Eden區中全部存活的對象都會被複制到「To」,而在「From」區中,仍存活的對象會根據他們的年齡值來決定去向。
- 年齡達到必定值(年齡閾值,能夠經過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到「To」區域
- 通過此次GC後,Eden區和From區已經被清空。這個時候,「From」和「To」會交換他們的角色,也就是新的「To」就是上次GC前的「From」
- 新的「From」就是上次GC前的「To」。
- 無論怎樣,都會保證名爲To的Survivor區域是空的。Minor GC會一直重複這樣的過程,直到「To」區被填滿,「To」區被填滿以後,會將全部對象移動到年老代中。
  1. 大對象直接進入老年代 :很長的字符串以及數組
  2. 長期存活的對象進入老年代 -XX:MaxTenuringThreshold
  3. 動態對象年齡斷定 :若是在Survivor 中,相同年齡全部對象的大小總和大於 Survivor 空間的一半, 大於或等於此年齡的對象就能夠直接進入老年代。
  4. 分配擔保機制

    1. 檢查老年代最大可用連續空間 與 新生代全部對象的總空間 --> yes --> MinorGc
    2. HandlePromotionFailure 是否容許擔保失敗 --> yes --> 檢查老年代最大可用連續空間是否大於歷次晉升到老年代對象的平均大小 --> MinorGC
2. Minor GC ,Full GC 觸發條件

Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
(1)調用System.gc時,系統建議執行Full GC,可是沒必要然執行
(2)老年代空間不足
(3)方法去空間不足
(4)經過Minor GC後進入老年代的平均大小大於老年代的可用內存
(5)由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小

3. 垃圾收集器

1. CMS (Concurrent Mark Sweep)
  1. 4個步驟:
  • 初始標記:標記 GC ROOTS 能夠直接關聯的對象
  • 併發標記:GC TRACING
  • 從新標記:修正併發標記期間,用戶程序繼續動做而致使的標記產生變更的那一部分對象的標記記錄
  • 併發清除
  1. 3個缺點:
  • 對 CPU 資源很是敏感
  • 沒法處理浮動垃圾(併發清理階段,用戶線程仍舊在運行,所以一直在產生垃圾,而沒法在當次收集中處理掉它們)
  • 產生大量的空間碎片
2. G1 (Garbage-First)

4個特色:

  • 並行與併發: 使用多個 CPU 或 CPU 核心來縮短 Stop-The-World 停頓的時間
  • 分代收集
  • 空間整合: 基於標記-整理算法
  • 可預測的停頓: 能夠創建可預測的停頓時間模型,讓使用者明確指定在一個長度爲 M 毫秒的時間片斷內,消耗在垃圾收集上的時間不超過 N 毫秒。

4個步驟:

  • 初始標記
  • 併發標記
  • 最終標記
  • 篩選回收: 首先對各個 Region 的回收價值和成本進行排序,根據用戶所指望的 GC 停頓時間來制定回收計劃。

G1的GC模式

Young GC:選定全部年輕代裏的Region。經過控制年輕代的region個數,即年輕代內存大小,來控制young GC的時間開銷。
Mixed GC:選定全部年輕代裏的Region,外加根據global concurrent marking統計得出收集收益高的若干老年代Region。在用戶指定的開銷目標範圍內儘量選擇收益高的老年代Region。

注意:Mixed GC不是full GC,它只能回收部分老年代的Region,若是mixed GC實在沒法跟上程序分配內存的速度,致使老年代填滿沒法繼續進行Mixed GC,就會使用serial old GC(full GC)來收集整個GC heap。

global concurrent marking:相似CMS,爲Mixed GC提供標記服務。
四個過程:

  • 初始標記(initial mark,STW)。它標記了從GC Root開始直接可達的對象。
  • 併發標記(Concurrent Marking)。這個階段從GC Root開始對heap中的對象標記,標記線程與應用程序線程並行執行,而且收集各個Region的存活對象信息。
  • 最終標記(Remark,STW)。標記那些在併發標記階段發生變化的對象,將被回收。
  • 清除垃圾(Cleanup)。清除空Region(沒有存活對象的),加入到free list。

G1 中的幾個重要概念 -- 原文連接--美團點評

1、Region

傳統的GC收集器將連續的內存空間劃分爲新生代、老年代和永久代(JDK 8去除了永久代,引入了元空間Metaspace),這種劃分的特色是各代的存儲地址(邏輯地址,下同)是連續的。

以下圖所示:

而G1的各代存儲地址是不連續的,每一代都使用了n個不連續的大小相同的Region,每一個Region佔有一塊連續的虛擬內存地址。以下圖所示:

在上圖中,咱們注意到還有一些Region標明瞭H,它表明Humongous,這表示這些Region存儲的是巨大對象(humongous object,H-obj),即大小大於等於region一半的對象。H-obj有以下幾個特徵:

  • H-obj直接分配到了old gen (老年代),防止了反覆拷貝移動。
  • H-obj在global concurrent marking 階段的 cleanupfull GC 階段回收。
  • 在分配H-obj以前先檢查是否超過 initiating heap occupancy percent和the marking threshold, 若是超過的話,就啓動 global concurrent marking,爲的是提前回收,防止 evacuation failures 和 full GC。
    爲了減小連續H-objs分配對GC的影響,須要把大對象變爲普通的對象,建議增大Region size。

2、SATB

全稱是Snapshot-At-The-Beginning,由字面理解,是GC開始時活着的對象的一個快照。它是經過Root Tracing獲得的,做用是維持併發GC的正確性。

那麼它是怎麼維持併發GC的正確性的呢?根據三色標記算法,咱們知道對象存在三種狀態:

  • 白:對象沒有被標記到,標記階段結束後,會被當作垃圾回收掉。
  • 灰:對象被標記了,可是它的field尚未被標記或標記完。
  • 黑:對象被標記了,且它的全部field也被標記完了。

因爲併發階段的存在,Mutator(更改器和)Garbage Collector線程同時對對象進行修改,就會出現白對象漏標的狀況,這種狀況發生的前提是:

  • Mutator賦予一個黑對象該白對象的引用。
  • Mutator刪除了全部從灰對象到該白對象的直接或者間接引用。

對於第一個條件,在併發標記階段,若是該白對象是new出來的,並無被灰對象持有,那麼它會不會被漏標呢?Region中有兩個top-at-mark-start(TAMS)指針,分別爲prevTAMS和nextTAMS。在TAMS以上的對象是新分配的,這是一種隱式的標記。
對於在GC時已經存在的白對象,若是它是活着的,它必然會被另外一個對象引用,即條件二中的灰對象。若是灰對象到白對象的直接引用或者間接引用被替換了,或者刪除了,白對象就會被漏標,從而致使被回收掉,這是很是嚴重的錯誤,因此SATB破壞了第二個條件。
也就是說,一個對象的引用被替換時,能夠經過 write barrier 將舊引用記錄下來。(並無 看懂在說什麼)

SATB也是有反作用的,若是被替換的白對象就是要被收集的垃圾,此次的標記會讓它躲過GC,這就是float garbage。由於SATB的作法精度比較低,因此形成的float garbage也會比較多。


3、RSet

全稱是Remembered Set,是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些相似。
還有一種數據結構也是輔助GC的:Collection Set(CSet),它記錄了 GC要收集的Region集合 ,集合裏的Region能夠是任意年代的。
在GC的時候,對於old->young和old->old的跨代對象引用,只要掃描對應的CSet中的RSet便可。

Rset : 屬於points-into結構(誰引用了個人對象)
Card Table : 則是一種points-out(我引用了誰的對象)的結構
G1的RSet是在Card Table的基礎上實現的:每一個Region會記錄下別的Region有指向本身的指針,並標記這些指針分別在哪些Card的範圍內。
這個RSet實際上是一個Hash Table,Key -- 別的Region的起始地址Value是一個集合 -- 裏面的元素是Card Table的Index

這裏解釋一下 :
上圖有三個 Region 。紅色表明 Rset , 灰色大方框表明 Card Table。
Region2 的 Rset2 中有兩個 Region 的起始地址,分別指向 Region1 , Region3。 -- 表明 Region1 與 Region3 引用了個人對象。
Region1 的 Card Table 位置上,存在一個 對 Region2 的引用。 -- 表明 Region1 引用了 Region2 的對象。
Region3 同理。

做用:
在作YGC(Minor GC)的時候,只須要選定young generation region的RSet做爲根集,這些RSet記錄了old->young的跨代引用,避免了掃描整個old generation。
而mixed gc的時候,old generation中記錄了old->old的RSet,young->old的引用由掃描所有young generation region獲得,這樣也不用掃描所有old generation region。因此RSet的引入大大減小了GC的工做量。


4、Pause Prediction Model

G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.

G1 GC是一個響應時間優先的GC算法,它與CMS最大的不一樣是,用戶能夠設定整個GC過程的指望停頓時間,參數'-XX:MaxGCPauseMillis'指定一個G1收集過程目標停頓時間,默認值200ms。
G1 經過這個模型統計計算出來的歷史數據來預測本次收集須要選擇的Region數量,從而儘可能知足用戶設定的目標停頓時間。
停頓預測模型是以衰減標準誤差爲理論基礎實現的。
這裏就不詳細介紹了,有興趣的,能夠看 美團大神的文章

4.2 Java 內存

  1. 程序計數器
  2. 虛擬機棧 : 局部變量表、操做數棧、動態連接、方法出口
  3. 本地方法棧 : native 方法
  4. 堆 : 全部的對象實例以及數組
  5. 方法區 : 已被加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼
  6. 運行時常量池 : 編譯期生成的各類字面量和符號引用
  7. 直接內存 : NIO類引入了一種基於通道(channel) 與 緩衝區(buffer) 的 I/O 方式,使用 Native 函數庫直接分配堆外內存 , 經過存儲在 Java 堆中的 DirectByteBuffer 對象做爲這塊內存的引用進行操做。

1. Java 對象的內存佈局

  1. 對象頭 : 哈希碼(2bit)-分代年齡(4)、輕量級鎖定(標誌位 00)、重量級鎖定、GC標記、可偏向(標誌位 01)補充: 類型指針、數組長度
  2. 實例數據 :
  3. 對齊填充

2. OOM 異常

  1. 堆溢出: 不斷建立對象,而且存在可達路徑,不被清除。那麼對象在達到最大堆容量限制後就會產生內存溢出
    經過 內存映像分析工具 (Eclipse Memory Analyzer) 對 Dump 出來的堆轉存儲快照進行分析。判斷是內存泄漏仍是內存溢出。
  2. 虛擬機棧與本地方法棧溢出:
    若是線程請求的棧深度大於虛擬機所容許的最大深度,將拋出 StackOverFlowError 異常。

若是虛擬機在擴展棧時沒法申請到足夠的內存空間,則拋出 OutOfMemoryError 異常。

  1. 方法區
    方法區存放 Class 的相關信息。若是存在大量的類 填滿 方法區。則會產生溢出。

經過 動態代理 或 經過 CGLIB 動態生成大量的類,以及大量 JSP與 動態JSP 文件的應用 。

3. OOM 異常的解決

一. 可經過命令按期抓取heap dump或者啓動參數OOM時自動抓取heap dump文件。
二. 經過對比多個heap dump,以及heap dump的內容,分析代碼找出內存佔用最多的地方。
三. 分析佔用的內存對象,是不是由於錯誤致使的內存未及時釋放,或者數據過多致使的內存溢出。

4.3 類加載器

1. 類加載過程

  • 加載 :能夠經過自定義類加載器參與
  1. 經過一個類的全限定名獲取定義此類的二進制字節流
  2. 將這個字節流表明的靜態存儲結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個表明這個類的 java.lang.Class 對象,做爲方法區這個類的各類數據的訪問入口
  • 驗證
  1. 文件格式驗證
  2. 元數據驗證 : 語義校驗
  3. 字節碼驗證 :邏輯校驗
  4. 符號引用驗證 :發生在解析階段中,將符號引用轉化爲直接引用
  • 準備 : 爲類變量分配內存並設置類變量初始值的階段
  • 解析 : 將符號引用 替換 爲直接引用的過程。
  • 初始化 : <clinit>() 類構造器 : 將類中的賦值語句與靜態代碼塊合併而成 -- <init>() 實例構造器

2. 雙親委派模型

  啓動(Bootstrap)類加載器:採用 C++ 實現,它負責將 <Java_Runtime_Home>/lib下面的核心類庫或-Xbootclasspath選項指定的jar包加載到內存中。因爲啓動類加載器到本地代碼的實現,開發者沒法直接獲取到啓動類加載器的引用,因此不容許直接經過引用進行操做。編寫自定義類加載器時,若是須要把加載請求委派給啓動類加載器,直接使用 null 代替.
  擴展(Extension)類加載器:擴展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將< Java_Runtime_Home >/lib/ext或者由系統變量-Djava.ext.dir指定位置中的類庫加載到內存中。開發者能夠直接使用標準擴展類加載器。
  系統(System)類加載器:系統類加載器是由 Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑java -classpath或-Djava.class.path變量所指的目錄下的類庫加載到內存中。開發者能夠直接使用系統類加載器。

工做過程:
若是一個類加載器收到了類的加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成。直到頂層的啓動類加載器中,當父加載器反饋本身沒法完成這個加載請求時,子加載器會嘗試本身去加載。

3. 線程上下文類加載器

方便 JNDI 服務:SPI 的接口是 Java 核心庫的一部分,是由引導類加載器來加載的;SPI 實現的 Java 類通常是由系統類加載器來加載的。引導類加載器是沒法找到 SPI 的實現類的,由於它只加載 Java 的核心庫。它也不能代理給系統類加載器,由於它是系統類加載器的祖先類加載器。也就是說,類加載器的代理模式沒法解決這個問題。
解決方法:Java 應用的線程的上下文類加載器 默認 就是系統上下文類加載器。在 SPI 接口的代碼中使用線程上下文類加載器,就能夠成功的加載到 SPI 實現的類。線程上下文類加載器在不少 SPI 的實現中都會用到。
Java默認的線程上下文類加載器是系統類加載器(AppClassLoader)。如下代碼摘自sun.misc.Launch的無參構造函數Launch()。
能夠經過 java.lang.Thread類 的 setContextClassLoader() 設置。

4. OSGI(open service gataway initiative)

方便執部署的實現。能夠在不重啓服務器的狀況下,對其中的邏輯代碼進行更新。
由 父類加載器 與 Bundle 組成 , 每一個 Bundle 的功能都是 發佈 export 與依賴 import。從而造成複雜的網狀結構
原理:
OSGi 中的每一個模塊都有對應的一個類加載器。它負責加載模塊本身包含的 Java 包和類。
當它須要加載 Java 核心庫的類時(以 java開頭的包和類),它會代理給父類加載器(一般是啓動類加載器)來完成。
當它須要加載所導入的 Java 類時,它會代理給導出此 Java 類的模塊來完成加載。

5. Tomcat

在雙親委派模型的基礎上加入了 Common類加載器,Catalina類加載器,Shared類加載器,WebApp類加載器,Jsp類加載器
Common類加載器, /common 目錄 被 Tomcat 與 因此 Web 應用程序共同使用
Catalina類加載器, /server 目錄中, 被 Tomcat 使用
Shared類加載器, /shared 目錄中 ,被全部 Web 應用程序共同使用
WebApp類加載器,Jsp類加載器 , /WebApp/WEB-INF 目錄中,只能被此 Web 應用程序使用。

4.4 解釋器與編譯器

1. 編譯模式

  1. Mixed Mode -- 混合模式
    默認爲混合模式,解釋器與編譯器搭配使用。
  2. Interpreted Mode -- 解釋模式
    使用 「-Xint" 參數。只使用解釋。
  3. Compiled Mode -- 編譯模式
    使用 「-Xcomp" 參數。 優先採用編譯,當編譯沒法進行時,使用解釋。

-version 命令,能夠輸出顯示這三種模式

2. 分層編譯(Tiered Compilation)

JDK1.7 中的 Server 模式虛擬機中被做爲默認編譯策略。

  1. 0層,程序解釋執行,解釋器不開啓性能監控功能(Profiling),可觸發第一層編譯
  2. 1層,也叫C1 編譯(下文有解釋),將字節碼編譯爲本地代碼,進行簡單、可靠的優化
  3. 2層,C2編譯。

3. OSR編譯

由於存在屢次執行的循環體,因此觸發 OSR 編譯,以整個方法 做爲編譯對象。
發生在方法執行過程當中,因此叫( On Stack Replacement ) 方法棧幀還在棧上,方法就被替換了。

4. 編譯對象以及觸發條件

熱點代碼的分類:

  1. 被屢次調用的方法
  2. 被屢次執行的方法體 -- OSR 編譯

熱點探測(Hot Spot Detection)

  1. 基於採樣 : 若是週期性的檢查各個線程的棧頂,若是發現某個方法常常出如今棧頂,則這個方法就是「熱點方法」。
  2. 基於計數器 -- HotSpot 虛擬機中採用。

    • 原理: 爲每一個方法創建計數器,統計方法的次數,若是執行次數超過必定的閾值,就認爲它是「熱點方法」
    • 計數器分類:

      • 方法調用計數器(Invocation Counter) :

        **統計一段時間內**,方法被調用的次數,若是超過期間限度,則將這個方法的調用計數器減小一半,稱爲**衰減**
      • 回邊計數器(Back Edge Counter) : 統計一個方法中循環體被執行的次數 -- OSR 編譯

        在字節碼中遇到控制流向後跳轉的指令,稱爲回邊。

5. 優化措施

hotspot中內嵌有2個JIT編譯器,分別爲Client Compiler,Server Compiler,但大多數狀況下咱們稱之爲C1編譯器和C2編譯器。

5.1 C1 編譯器

client compiler,又稱C1編譯器,較爲輕量,只作少許性能開銷比較高的優化,它佔用內存較少,適合於桌面交互式應用。
在寄存器分配策略上,JDK6之後採用的爲線性掃描寄存器分配算法,其餘方面的優化,主要有方法內聯、去虛擬化、冗餘消除等。

A、方法內聯

多個方法調用,執行時要經歷屢次參數傳遞,返回值傳遞及跳轉等,C1採用方法內聯,把調用到的方法的指令直接植入當前方法中。-XX:+PringInlining來查看方法內聯信息,-XX:MaxInlineSize=35控制編譯後文件大小。

B、去虛擬化

是指在裝載class文件後,進行類層次的分析,若是發現類中的方法只提供一個實現類,那麼對於調用了此方法的代碼,也能夠進行方法內聯,從而提高執行的性能。

C、冗餘消除

在編譯時根據運行時情況進行代碼摺疊或消除。

5.2 C2 編譯器

Server compiler,稱爲C2編譯器,較爲重量,採用了大量傳統編譯優化的技巧來進行優化,佔用內存相對多一些,適合服務器端的應用。和C1的不一樣主要在於寄存器分配策略及優化範圍.
寄存器分配策略上C2採用的爲傳統的圖着色寄存器分配算法,因爲C2會收集程序運行信息,所以其優化範圍更多在於全局優化,不只僅是一個方塊的優化。
收集的信息主要有:分支的跳轉/不跳轉的頻率、某條指令上出現過的類型、是否出現過空值、是否出現過異常等

逃逸分析(Escape Analysis) 是C2進行不少優化的基礎,它根據運行狀態來判斷方法中的變量是否會被外部讀取,如不會則認爲此變量是不會逃逸的,那麼在編譯時會作標量替換、棧上分配和同步消除等優化。
若是證實一個對象不會逃逸到方法或線程以外,則:

- 棧上分配(Stack Allocation) :肯定不會逃逸到**方法外**,讓這個對象在棧上分配內存,對象佔用的內存空間能夠隨棧幀的出棧而銷燬。
- 同步消除(Synchronization Elimination) :肯定不會逃逸到**線程外**,則沒法被其餘線程訪問,因此能夠取消同步措施。
- 標量替換(Scalar Repalcement) : 
    標量(Scalar)指一個數據沒法再分解成更小的數據來表示 -- Java 中的原始數據類型
    聚合量(Aggregate)指一個數據能夠繼續分解 -- Java 中的對象
    **原理:**直接建立若干個能夠被方法使用的成員變量來替代。
5.3 其餘措施(注: 不知是 C1 仍是 C2)
  1. 語言無關的經典優化技術 -- 公共子表達式消除(Common Subexpression Elimination)

若是一個表達式E 已經計算過,而且從先前的計算 到如今 值不曾改變,那麼若是 E 再次出現,則能夠直接使用以前的表達式結果,代替 E 。

  1. 語言相關的經典優化技術 -- 數組邊界檢查消除(Array Bounds Checking Elimination)
    這個不是很瞭解,作一個重點。。。 之後整理

4. 零散知識點

1. 靜態多分派與動態單分派

靜態分派 : 依靠靜態類型 定位方法。
編譯階段:Human man = new Man(); // 靜態類型爲 Human
運行階段:man.sayHello() // 動態類型爲 Man

重載的優先級
sayHello(char arg);
char -> int -> long -> float -> double // 不可轉化爲 byte short , 由於char 轉化是不安全的。
-> Character -> Serializable/Comparable -> Object -> char...(變長參數)

宗量:方法的接收者與方法的參數統稱爲宗量
單分派 根據一個宗量對目標方法進行選擇
多分派 根據多個宗量對目標方法進行選擇

public class QQ{};
public class _360{};
public static class Father {
    public void hardChoice(QQ arg);
    public void hardChoice(_360 arg);
}
public static class Son extends Father{
    public void hardChoice(QQ arg);
    public void hardChoice(_360 arg);
}

Father father = new Father();
Father son = new Son();

// 靜態多分派 - 編譯 : 方法的接收者 Father - Son, 參數 QQ - _360
father.hardChoice(_360);
// 動態多分派 - 運行 : 已經肯定 參數爲 QQ ,再判斷 實際類型 , son的實際類型爲 Son 。
son.hardChoice(QQ);

結語

都看到這裏了,點個關注,點波再走,QAQ。
你的小手輕點,是我最大的動力哦。

一隻想當程序員的1米88處女座大可愛如此說道。

相關文章
相關標籤/搜索