轉《深入理解Java虛擬機器》學習筆記之最後總結

來源:互聯網
上載者:User

標籤:

編譯器

Java是編譯型語言,按照編譯的時期不同,編譯器可分為:

  1. 前端編譯器:其實叫編譯器的前端更合適些,它把*.java檔案轉變成*.class檔案,如Sun的Javac、Eclipse JDT中的增量式編譯器ECJ;
  2. JIT編譯器:虛擬機器的後端運行期編譯器(Just In Time Compiler),它把位元組碼轉變成機器碼,如HotSpot VMd C1、C2編譯器;
  3. AOT編譯器:靜態提前編譯器(Ahead Of Time Compiler),它直接把*.java檔案編譯成本地機器碼,如GUN Compiler for the Java(GCJ)、Excelsior JET;

前端編譯器做了許多針對編碼過程的最佳化措施來改善程式員的編碼風格和提高編碼效率,如相當多新生的Java文法特性,都是靠前端編譯器的“文法糖”來實現 的;而虛擬機器設計團隊把效能的最佳化集中到了JIT編譯器,這樣可以讓那些不是有Javac產生的Class檔案也同樣能享受到編譯器最佳化所帶來的好處。

Java程式最初是通過解譯器(Interpreter)進行解釋執行的,後來在部分的商用虛擬機器(Sun HotSpot、IBM J9)中,當虛擬機器發現某個方法或代碼塊的運行特別頻繁,就會把這些代碼認定為“熱點代碼(Hot Spot Code)”,為了提高熱點代碼的執行效率,在運行時,虛擬機器將會把這些代碼編譯成與本地平台相關的機器碼,並進行各種層次的最佳化,完成這個任務的編譯器 稱為即時編譯器(Just In Time Compiler)。

儘管並不是所有的Java虛擬機器都採用解譯器和編譯器並存的架構,但許多主流的商用虛 擬機如HotSpot、J9等都同時包含解譯器和編譯器:當程式需要迅速啟動和執行的時候,解譯器可以首先發揮作用,省去編譯的時間,立即執行。當程式運 行後,隨著時間的推移,編譯器逐漸發揮作用,把越來越多的代碼編譯成本地代碼之後,可以擷取更高的執行效率。

HotSpot虛擬機器中內建了兩個即時編譯器分別是Client Compiler和Server Compiler,或者分別稱為C1和C2。但無論是採用的編譯器是Client Compiler還是Server Compiler,解譯器和編譯器搭配使用的方式在虛擬機器中都被稱為“混合模式(Mixed Mode)”,不過可以通過參數-Xint強制虛擬機器運行於“解釋模式(Interpreted Mode)”,此時編譯器完全不介入工作,全部代碼都使用解釋方式執行;另外也可以使用參數-Xcomp強制虛擬機器運行於“編譯模式(Compiled Mode)”,這時將優先採用編譯方式執行,但解譯器仍然要在編譯無法進行的情況下介入執行。

編譯最佳化

由於即時編譯器編譯本地代碼需要佔用程式已耗用時間,要編譯出最佳化程度更高的代碼,所花 費的時間就可能越長;而且想要編譯出最佳化程度更高的代碼,解譯器可能還要替編譯器收集效能監控資訊,這對解釋執行的速度也有影響。為了在程式啟動響應速度 和運行效率之間達到最佳平衡,HotSpot虛擬機器將會逐漸啟用分層編譯策略,分層編譯的概念在JDK1.6時期出現,後來一直處於改進階段,最終在 JDK1.7的Server模式虛擬機器中作為預設編譯策略開啟。分層編譯根據編譯器編譯、最佳化的規模與耗時,劃分出不同的編譯層次,其中包括:

  • 第0層:程式解釋執行,解譯器不開啟效能監控功能(Profiling),可觸發第1層編譯;
  • 第1層:也稱為C1編譯,將位元組碼編譯為本地代碼,進行簡單可靠的最佳化,如有必要將加入效能監控的邏輯;
  • 第2層(或2層以上):也稱為C2編譯,也是將位元組碼編譯為本地代碼,但是會啟用一些編譯耗時較長的最佳化,甚至會根據效能監控資訊進行一些不可靠的激進最佳化;

實施分層編譯後,Client Compiler和Server Compiler將會同時工作,許多代碼都可能會被多次編譯,用Client Compiler擷取更高的編譯速度,用Server Compiler來擷取更好的編譯品質,在解釋執行的時候也無需再承擔收集效能監控資訊的任務。

在運行過程中被即時編譯器編譯的“熱點代碼”有兩類:被多次調用的方法和被多次執行的迴圈體,但不管是那種都是以整個方法作為編譯對象的,因為這種編譯方式發生在方法的執行過程中,因此be形象地稱為棧上替換(On Stack Replacement,OSR)。

要知道一段代碼是不是熱點代碼,是不是需要觸發即時編譯,這個行為稱為熱點探測,目前主要的熱點探測判定方式有兩種:

  • 基於採樣的熱點探測:虛擬機器會周期性地檢查各個線程的棧頂,如果發現某個(或某些) 方法經常出現在棧頂,那這個方法就是“熱點方法”。基於採樣的熱點探測優點是實現簡單高效;缺點是結果不精準。(比如某個線程阻塞了,棧頂一直是方法A, 虛擬機器周期性採樣都只是探測到這個方法A……)
  • 基於計數器的熱點探測:採用這種方法的虛擬機器會為每個方法(甚至是代碼塊)建立計數器,統計方法的執行次數,如果執行次數超過一定的閾值就認定它是“熱點方法”。

HotSpot虛擬機器中使用的是第二種——基於計數器的熱點探測方法,因此它為每個方法準備了兩個計數器:方法調用計數器和回邊計數器。在確定了虛擬機器運行參數的前提下,這兩個計數器都有一個確定的閾值,當計數器超過了閾值,就會觸發JIT編譯。

方法內聯編譯器最佳化重要最佳化手段,因為它不僅消除了方法調用成本,更重要的意義是它是其他最佳化手段的基礎,因為只有把代碼集中了才能更方便和有效地進行最佳化。如果想查看即時編譯的情況可以使用參數:-XX:+PrintCompilation。

JVM記憶體模型

主記憶體和工作記憶體

  • 所有的變數都儲存在主記憶體中
  • 每個線程都還有自己的工作記憶體,擁有主記憶體的對象的拷貝
  • 線程只能操作自己的工作記憶體,線程間的互動只能通過主記憶體通訊

記憶體間互動操作:java記憶體模型定義了8種原子操作,jvm要報保證每一個操作為原子操作:

  1. lock(鎖定,作用於主記憶體的變數);
  2. unlock(解鎖,作用於主記憶體的變數);
  3. read(讀取,作用於主記憶體);
  4. load(載入,放入到工作記憶體中);
  5. use(作用於工作記憶體的變數);
  6. assign(賦值,作用於工作記憶體);
  7. store(儲存,作用於工作記憶體的變數);
  8. write(寫入,作用於主記憶體的變數);

 

如果要把一個變數從主內容複寫到工作記憶體,那就要順序地執行read和load操作,如果把變數從工作記憶體同步回主記憶體,就要順序地執行store和write操作。注意java記憶體模型只要求上述兩個操作必須按順序執行,而沒有保證是連續執行。
以上8中操作必須滿足的規則:

  1. 不允許出現read和load,store和write操作之一單獨出現
  2. 不允許一個線程丟棄他的最近的assign操作
  3. 不允許一個線程無原因的把資料從線程的工作記憶體同步到主記憶體中
  4. 一個新變數只能在主記憶體中”誕生”,不允許在工作記憶體中直接使用一個未被初始化的變數
  5. 一個變數在同一個時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重複執行多次,多次執行lock後,只有執行相同次數的unlock操作,變數才會解鎖;
  6. 如果對一個變數執行lock操作,那將會清空工作記憶體中此變數的值,在執行引擎使用這個變數之前,需要重新執行load或assign操作初始設定變數的值;
  7. 如果一個變數事先沒有被lock伺服器用戶端檔案鎖,那就不允許對他執行unlock操作
  8. 對一個變數執行unlock之前,必須先把變數同步會主記憶體(執行store,write);

volatile
volatile--java虛擬機器提供的輕量級的同步機制。2個重要特性。1是保證此變數對所有線程可見,2是禁止指令重排序最佳化,

  • 每次使用之前都要先重新整理,執行引擎看不到不一致的情況,保證可見度;但volatile變數在各個線程的工作記憶體中不存在一致性問題(也可以存在不一致的情況),但java裡面的運算並非原子操作,導致volatile變數的運算在並發下一樣是不安全的。(java記憶體模型規定,load和use動作連續,store和write動作連續)
  • 指令重排從硬體上來講,指令重排序指CPU採用了允許將多條指令不按程式規定的順序分開發送給各相應電路單元處理。但並不是說指令任意重排,CPU需要能正確處理指示依賴情況以保障程式能得出正確的執行結果。

效能方面:volatile的讀操作的效能消耗與普通變數幾乎沒有差別,但是寫操作則可能會慢一些,因為他需要在本地代碼中插入許多記憶體屏蔽指令來保證處理器不發生亂序執行。

以下情境仍需要同步:

  • 運算結果並不依賴變數的當前值,或者能夠保證只有單一的線程修改變數的值;
  • 變數不需要與其他的狀態變數共同參與不變約束;

原子性、可見度和有序性(總結):

  • 原子性:read,load,assign,use,write
  • 可見度:java記憶體模型是通過變數修改後將新值同步回主存,在變數讀取前從主存重新整理變數值這種依賴主存作為傳遞媒介的方式來實現可見度的。volatile,synchronized,final(this引用逃逸除外;)
  • 有序性:如果在本地線程內觀察,所有操作都是有序的;如果在一個線程中觀察另一個線程,所有的操作都是無序的;

逃逸分析:當變數(或者對象)在方法中分配後,其指標有可能被返回或者被全域引用,這樣就會被其他過程或者線程所引用,這種現象稱作指標(或者引用)的逃逸(Escape)。(來自互連網)

現行發生原則

指的是java記憶體模型中定義的兩項操作之間的偏序關係,如果說操作A先行發生於操作B,其實就是說在發生操作B之前,操作A產生的影響能被操作B觀察到,“影響”包括修改了記憶體中共用變數的值、發送了訊息、調用了方法等。

java記憶體模型下的“天然的”先行發生關係:

  1. 程式次序規則:一個線程,代碼順序(控制流程順序);
  2. 管程鎖定規則:
  3. volatile變數規則:寫操作先行發生於後面的讀操作
  4. 線程啟動規則:start()方法先行發生於此線程的每一個動作;
  5. 線程終止規則:join
  6. 線程中斷規則:
  7. 對象終結規則;初始化先於finalize();
  8. 傳遞性;

線程狀態

  • 建立new
  • 運行runable
  • 無期限等待waiting
  • 期限等待timed waiting
  • 阻塞blocked
  • 結束terminated

安全執行緒的實現方法

  • 互斥同步(阻塞同步):互斥是因,同步是果;互斥是方法,同步是目的
    java.util.concurrent.ReentranLock,比synchron增加了一些進階特性:等待可中斷、可實現公平鎖、以及鎖可以綁定多個條件;
  • 非阻塞同步:(通俗的說就是不斷地重試,知道成功為止),樂觀的並發策略,需要硬體指令集的支援;

鎖最佳化

    • 自旋鎖與自適應自選(cas)
    • 鎖消除(判定依據是逃逸分析),String類,字串相加,JDK1.5之前轉化為StringBuffer類(安全執行緒);JDK1.5及以後,之後會StringBuilder
    • 鎖粗化,範圍擴大
    • 輕量級鎖,jDK1.6加入,
    • 偏向鎖

轉《深入理解Java虛擬機器》學習筆記之最後總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.