本篇博客主要針對Java虛擬機的晚期編譯優化,Java內存模型與線程,線程安全與鎖優化進行總結,其他部分總結請點擊Java虛擬總結上篇 ,Java虛擬機總結中篇。算法
一.晚期運行期優化
即時編譯器JIT
即時編譯器JIT的做用就是熱點代碼轉換爲平臺相關的機器碼,並進行優化,它並非一個虛擬機所必須的部分,只能說有它是錦上添花。segmentfault
熱點代碼
熱點代碼分類
熱點探測斷定方法
- 基於採樣的熱點探測,虛擬機週期性地檢查棧頂,發現某個方法常常出如今棧頂,那麼這個方法就是熱點方法,簡單高效但不精確
- 基於計數器熱點探測,爲每一個方法創建計數器來統計執行次數,超過閾值就是熱點方法,Hotpot就是採用這種方法。分爲方法計數器(統計方法),回邊計數器(統計循環)
編譯過程(Client Complier)
-
第一階段數組
-
第二階段緩存
-
第三階段安全
- 使用線性掃描算法,在LIR上分配寄存器,產生機器代碼
優化方法
公共子表達式優化
當一個表達式A的結果已經計算過了,且A中的全部變量都沒有發生過變化,那麼下一次要用到A時就不用計算了,而是直接取以前A的結果。併發
數組邊界檢查消除
方法內聯
逃逸分析
逃逸的定義:一個在方法裏定義的變量,做爲參數傳遞給其餘方法(方法逃逸),或者賦值給類變量(線程逃逸)。性能
優化方法:優化
- 棧上分配:不會逃逸的對象就不在堆上分配了,就在棧上分配,那麼對象所佔的空間就能夠隨棧幀的出棧而銷燬,減小垃圾收集系統的壓力。
- 同步消除:若是一個變量確定不會逃逸出線程,那麼關於這個變量的同步措施就能夠去掉。
二.Java內存模型與線程
內存模型
說了這麼多的內存模型,到底什麼是內存模型呢?操作系統
特定的操做協議下,對特定的內存或高速緩存進行讀寫訪問的過程抽象。
它的做用是定義程序中各個共享的變量的訪問規則,即如何將變量寫入內存和從內存中取出變量。Java內存模型有主內存與工做內存之分,全部變量存在主內存中,線程則是擁有本身的工做內存,它是主內存的副本拷貝,線程只能讀寫工做內存。線程
8種原子操做
- lock(鎖定):做用於主內存的變量,它把一個變量標識爲一條線程獨佔的狀態。
- unlock(解鎖):做用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。
- read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工做內存中,以便隨後的 load 動做使用。
- load(載入):做用於工做內存的變量,它把 read 操做從主內存中獲得的變量值放入工做內存的變量副本中。
- use(使用):做用於工做內存的變量,它把工做內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量的值的字節碼指令時將會執行這個操做。
- assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
- store(存儲):做用於工做內存的變量,它把工做內存中一個變量的值傳送到主內存中,以便隨後的 write 操做使用。
- write(寫入):做用於主內存的變量,它把 store 操做從工做內存中獲得的變量的值放入主內存的變量中。
volatile變量的特殊規則
volatile的特性是保證此變量對全部線程的可見性,即當變量的值修改後,其餘線程能夠當即知道發生的變化。普通變量則是修改完值後,須要寫回主內存,而後其餘線程再從主內存讀取該數據。volatile還能夠經過內存屏障來禁止指令的重排序。綜合來說它的讀操做和普通變量差很少,寫操做慢一點。
long和double變量的特殊規則
8種操做通常都是原子性的,可是對於64位的數據,內存模型容許將沒有被volatile修飾的64位數據的讀寫操做劃分爲兩次32位的操做進行---->非原子協定但通常咱們不須要將long和double聲明爲volatile。
先行發生原則
- 程序次序規則
- 管程鎖定規則
- volatile變量規則
- 線程啓動規則
- 線程終止規則
- 線程中斷規則
- 對象終結規則
- 傳遞性
Java與線程
Java的Thread類大多API都是Native方法,是與平臺相關的。
實現線程的三種方式
- 使用內核線程實現:內核線程即直接由操做系統內核支持的線程,由內核來完成線程切換,程序使用輕量級進程接口與內核線程一對一的關係,內核線程再經由線程調度器分派給CPU。
- 使用用戶線程實現:用戶線程的創建同步銷燬調度徹底在用戶態中完成,不需切換到內核態,一對多的關係。
- 用戶線程+輕量級進程:多對多的關係。
線程的調度
-
協同式調度
- 線程的執行時間由線程本身控制,執行完後再主動通知系統切換線程,可能會致使一個線程長時間地阻塞
-
搶佔式調度
- 由系統分配時間,線程能夠主動讓出時間可是不能主動得到時間,經過設置優先級肯定順序
線程的狀態
- 新建:剛剛建立還未啓動
- 運行:正在執行或者等待分配時間
- 無限等待:不會被CPU分配時間,須要其餘線程顯式喚醒
- 有限等待:在一段時間後由系統自動喚醒
- 阻塞:等待一個排他鎖
- 結束
三.線程安全與鎖優化
線程安全的程度,依次減弱
- 不可變,將對象中帶狀態的變量都置爲final
- 絕對線程安全,徹底符合線程安全定義
- 相對線程安全,對這個對象的單獨的操做是線程安全的,如Vector,HashTable等
- 線程兼容,對象自己不是線程安全的,可是能夠在調用端正確地使用同步手段才能保證在併發環境下正常使用。
- 線程對立,不管調用端如何努力,都不可能實現線程安全
線程安全的實現方法
-
互斥同步
synchronized關鍵字會在代碼塊的先後分別造成monitorenter和monitorexit指令,這兩個指令須要一個reference對象參數,該鎖有一個計數器以實現同步,進入時將計數器+1,退出時-1,本線程可重入,其餘線程需阻塞等待。synchronized的缺點是因爲Java線程是映射到操做系統的,因此喚醒阻塞一個線程都須要系統幫忙,須要從用戶態轉到內核態,耗費不少處理器時間。
ReentrantLock對synchronized的優點:
- 等待可中斷
- 公平鎖:必須按照申請鎖的時間順序來一次得到鎖
- 鎖綁定多個條件
- 非阻塞同步
爲了解決線程阻塞和喚醒所帶來的性能問題,先對共享數據進行操做,若是沒有競爭就成功了,不然就補償(不斷重試直到成功)
-
無同步方案
- 可重入代碼
- 線程本地存儲,把共享數據的範圍限制到線程內,ThreadLocalMap以ThreadLocalHashMap爲鍵,以本地線程變量爲值的K-V對
鎖優化
鎖優化的方案有如下幾種:
- 自旋鎖:爲了減小線程阻塞與喚醒的消耗,線程在被阻塞時能夠執行一個忙循環(自旋)
- 鎖消除:對不存在共享數據競爭的鎖進行消除
- 鎖粗化:在一個代碼塊內對一個對象連續的地加鎖解鎖,就對整個代碼塊一次性加鎖減小性能損耗
- 輕量級鎖:無競爭地狀況下使用CAS操做去消除同步使用地互斥量
- 偏向鎖:鎖會偏向於第一個得到它地線程