国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JVM(1)---虛擬機在運行期的優化策略

LiuZh / 2732人閱讀

摘要:被多次執行的循環體。數組范圍檢查消除。這種安全檢查策略可以避免溢出。不過,虛擬機還是挺聰明的,它會根據運行期收集到的信息來自動選擇最優方案。

1.解釋器與JIT編譯器

首先我們先來了解一下運行在虛擬機之上的解釋器JIT編譯器

當我們的虛擬機在運行一個java程序的時候,它可以采用兩種方式來運行這個java程序:

采用解釋器的形式,也就是說,在運行.class運行的時候,解釋器一邊把.class文件翻譯成本地機器碼,一邊執行。顯然這種一邊解釋翻譯一邊執行發方式,可以使我們立即啟動和執行程序,省去編譯的時間。不過由于需要一遍解釋翻譯,會讓程序的執行速度比較慢。

采用JIT編譯器的方式:注意,JIT編譯器是把.class文件翻譯成本地機器碼,而javac編譯器是把.java源文件編譯成.class文件。如果采用JIT編譯器的方式則是在啟動運行一個程序的時候,先把.class文件全部翻譯成本地機器碼,然后再來執行,顯然,這種方式在執行的時候由于不用對.clasa文件進行翻譯,所以執行的速度會比較快。當然,代價就是我們需要花銷一定的時間來把字節碼翻譯成本地機器碼。這樣,程序在啟動的時候,會有更多的延遲。

這兩種方式可以說是各有優勢,虛擬機(特指HotSpot虛擬機)在執行的時候,一般會采用兩種方式結合的策略。

也就是說,在程序執行的時候,有些代碼采用解釋器的方式,有些代碼采用編譯器,稱之為即時編譯。一般我們會對熱點代碼采用編譯器的方式。

2.編譯對象與觸發條件

上面已經說了,運行過程中,如果遇到熱點代碼就會觸發對該代碼進行編譯,編譯成本地機器碼。

什么是熱點代碼?

熱點代碼主要有一下兩類:

被多次調用的方法。

被多次執行的循環體。

不過這里需要注意的是,由于循環體是存在方法之中的,盡管編譯動作是由循環體觸發的,但編譯器仍然會以這個方法來作為編譯的對象。

3.熱點探測

判斷一段代碼是不是熱點代碼,是不是需要觸發即時編譯,這樣的行為我們稱之為熱點探測。熱點探測判定有以下兩種方式:

基于采樣的熱點探測:這種方式虛擬機會周期性著檢查各個線程的棧頂,如果發現某個方法經常出現在棧頂,那么這個方法就是熱點方法。可能有人會問,所謂經常,那什么樣才算經常,對于這個我只能告訴你,這個取決于你自己的設置,如果自己沒有進行相應的設置的話,就采用虛擬機的默認設置。

基于計數器的熱點探測:這種方法我們會為每個方法設置一個計數器,統計方法被調用的次數,如果到達一定的次數,我們就把它當作是熱點方法

兩種方法的優缺點

顯然第一種方法在實現上是比較簡單、高效的,但是缺點也很明顯,精確度不高,容易受到線程阻塞等別的外界因素的干擾。

第二種方式的統計結果會很精確,但需要為每個方法建立并維護一個計數器。實現上會相對復雜一點并且開銷也會大點。

不過,這里需要指出的是,我們的HotSpot虛擬機采用的是基于計數器的方式。

說明:虛擬機在執行方法的時候,會先判斷該方法是否存在已經編譯好的版本,如果存在,則執行編譯好的本地機器碼,否則,采用一邊解釋一邊編譯的方式。
4.編譯優化技術

先看一段代碼:

int a = 1;
if(false){
    System.out.println("無用代碼");
}
int b = 2;

對于這段代碼,我們都知道是if語句體里面的代碼是一定不可能會被執行到的,也就是說,這實際上是一段一點用處也沒有的代碼,在執行時只能浪費判斷時間。

實際上,對于我們書寫的代碼,編譯器在編譯的時候是會進行優化的。對于上面的代碼,編譯優化之后會變成這樣:

int a = 1;
int b = 2;

那段無用的代碼會被消除掉。

各種編譯優化策略

我們剛才已經說了,對于有些被多次調用的方法或者循環體,虛擬機會先把他們編譯成本地機器碼。由于這些熱點代碼都是一些會被多次重復執行的代碼,為了使得編譯好的代碼更加完美,運行的更快。編譯器做了很多的編譯優化策略,例如上面的無用代碼消除就是其中的一種。

下面我們來講講大概都有那些優化策略:

大概預覽一波:

公共子表達式消除。

數組范圍檢查消除。

方法內聯。

逃逸分析。

(1).公共子表達式消除

含義:如果一個表達式 E 已經計算過了,并且從先前的計算到現在 E 中的所有變量的值都沒有發生變化,那個 E 的這次出現就成為了公共子表達式。對于這樣的表示式,沒有必要對它再次進行計算了,直接沿用之前的結果就可以了。

我們來舉個例子。例如

int d = (c * b) * 10 + a + (a + b * c);

這段代碼到了即時編譯器的手里,它會進行如下優化:

表達式中有兩個 b * c的表達式,并且在計算期間b與c的值并不會變。所以這條表達式可能會被視為:

int d = E * 10 + a+ (a + E);

接著繼續優化成

int d = E * 11 + a + a;

接著

int d = E * 11 + 2a;

這樣,代碼在執行的時候,就會節省了一些時間了。

(2).數組范圍檢查消除

我們知道,java是一門動態安全的語言,對數組的訪問不像c/c++那樣,可以采用指針指向一塊可能不存在的區域。例如假如有一個數組arr[],在java語言中訪問數組arr[i]的時候,是會先進行上下界范圍檢查的,即先檢查i是否滿足i >= 0 && i < arr.length這個條件。如果不滿足則會拋出相應的異常。這種安全檢查策略可以避免溢出。但每次數組訪問都會進行這樣一次檢查無疑在速度性能上造成一定的影響。

實際上,對于這樣一種情況,編譯器也是可以幫助我們做出相應的優化的。例如對于數組的下標是一個常量的,如arr[2],只要在編譯期根據數據流分析來確定arr.length的值,并判斷下標‘2’并沒有越界,這樣在執行的時候就無需在判斷了。

更常見的情況是數組訪問發生在循環體中,并且使用循環變量來進行數組的訪問,對于這樣的情況,只要編譯器通過數據流就可以判斷循環變量的取值范圍是否在[0, arr.length)之內,如果是,那么整個循環中就可以節省很多次數組邊界檢測判斷的操勞了。

對于這些安全檢查所消耗的時間,實際上,我們還可以采用另外一種策略--隱式異常處理。例如當我們在訪問一個對象arr的屬性arr.value的時候,沒有優化之前虛擬機是這樣處理的:

if(arr != null){
    return arr.value;
}else{
    throw new NollPointException();
}

采用優化策略之后編程這樣子:

try{
    return arr.value;
}catch(segment_fault){
    uncommon_trap();
}

就是說,虛擬機會注冊一個Segment Fault信號的異常處理器(uncommon_trap()),這樣當arr不為空的時候,對value的訪問可以省去對arr的判斷。代價就是當arr為空時,必須轉入到異常處理器中恢復并拋出NullPointException異常,這個過程會從用戶態轉到內核態中處理,結束后在回到用戶態,速度遠比一次判斷空檢查慢。當arr極少為null的時候,這樣做是值得的,但假如arr經常為null時,那么會得不償失。

不過,虛擬機還是挺聰明的,它會根據運行期收集到的信息來自動選擇最優方案。

(3).方法內聯

先看一段代碼

public static void f(Object obj){
    if)(obj != null){
        System.out.println("do something");
    }
}

public static void test(String[] args){
    Object obj = null;
    f(obj);
}

對于這段代碼,如果把兩個方法結合在一起看,我們可以發現test()方法里面都是一些無用的代碼。因為f(obj)這個方法的調用,沒啥卵用。但是如果不做內聯優化,后續盡管進行了無用代碼的消除,也是無法發現任何無用代碼的,因為如果把f(Object obj)和test(String[] args)兩個發放分開看的話,我們就無法得只f(obj)是否有用了。

內聯優化后的代碼可以是這樣:

public static void f(Object obj){
    if)(obj != null){
        System.out.println("do something");
    }
}

public static void test(String[] args){
    Object obj = null;
    //該方法直接不執行了
}

(4).逃逸分析

逃逸分析是目前Java虛擬機比較前沿的優化技術,它并非是直接優化代碼,而是為其他優化手段提供依據發分析技術。

逃逸分析主要是對對象動態作用域進行分析:當一個對象在某個方法被定義后,它有可能被外部的其他方法所引用,例如作為參數傳遞給其他方法,稱之為方法逃逸,也有可能被外部線程訪問到,例如類變量,稱之為線程逃逸

假如我們可以證明一個對象并不會發生逃逸的話,我們就可以通過一些方式對這個變量進行一些高效的優化了。如下所示:

1).棧上分配

我們都知道一個對象創建之后是放在上的,這個對象可以被其他線程所共享,并且我們知道在堆上的對象如果不再使用時,虛擬機的垃圾收集系統就會對它進行帥選并回收。但無論是回收還是帥選,都是需要花費時間的。

但是假如我們知道這個對象不會逃逸的話,我們就可以直接在棧上對這個對象進行內存分配了,這樣,這個對象所占用的內存空間就可以隨進棧和出棧而自動被銷毀了。這樣,垃圾收集系統就可以省了很多帥選、銷毀的時間了。

2).同步消除

線程同步本身是一個相對耗時的過程,如果我們能判斷這個變量不會逃出線程的話,那么我們就可以對這個變量的同步措施進行消除了。

3).標量替換

什么是標量?

當一個數據無法分解成更小的時候,我們稱之為變量,例如像int,long,char等基本數據類型。相對地,如果一個變量可以分解成更小的,我們稱之為聚合量,例如Java中的對象。

假如這個對象不會發生逃逸。

我們可以根據程序訪問的情況,如果一個方法只是用到一個對象里面的若干個屬性,我們在真正執行這個方法的時候,我們可以不創建這個對象,而是直接創建它那幾個被使用到的變量來代替。這樣,不僅可以節省內存以及時間,而且這些變量可以隨出棧入棧而銷毀。

不過,對于編譯器優化的技術還有很多,上面這幾種算是比較典型的。

本次講解到這里。

參考書籍:深入Java虛擬機

如果你習慣在微信公眾號看技術文章
想要獲取更多資源的同學
歡迎關注我的公眾號:苦逼的碼農
每周不定時更新文章,同時更新自己算法刷題記錄。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76850.html

相關文章

  • JVM從小白學成大佬】2.Java虛擬機運行時數據區

    摘要:虛擬機在執行程序的過程中會把它所管理的內存劃分為若干個不同的數據區域。棧幀棧幀是用于支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧的棧元素。棧幀的概念結構如下運行時數據區腦圖高 這里我們先說句題外話,相信大家在面試中經常被問到介紹Java內存模型,我在面試別人時也會經常問這個問題。但是,往往都會令我比較尷尬,我還話音未落,面試者就會背誦一段(Java虛擬...

    shuibo 評論0 收藏0
  • 線程安全(上)--徹底搞懂volatile關鍵字

    摘要:此時,就出現了線程不安全問題了。因為的初始值會是因此,重排序是有可能導致線程安全問題的。真的能完全保證一個變量的線程安全嗎我們通過上面的講解,發現關鍵字還是挺有用的,不但能夠保證變量的可見性,還能保證代碼的有序性。 對于volatile這個關鍵字,相信很多朋友都聽說過,甚至使用過,這個關鍵字雖然字面上理解起來比較簡單,但是要用好起來卻不是一件容易的事。 這篇文章將從多個方面來講解vol...

    teren 評論0 收藏0
  • 在運行期通過反射了解JVM內部機制

    摘要:我們找到了許多有趣的工具和組件用來檢測狀態的各個方面,其中一個就是在運行期通過反射了解內部機制。由于包含多種的實現,就是供具體實現比如必須繼承的抽象類。調試器框架是可擴展的,這意味著可以通過繼承這個抽象類來使用另一個調試器。 在日常工作中,我們都習慣直接使用或者通過框架使用反射。在沒有反射相關硬編碼知識的情況下,這是Java和Scala編程中使用的類庫與我們的代碼之間進行交互的一種主要...

    crossea 評論0 收藏0
  • 我終于搞清楚了和String有關那點事兒。

    摘要:為了減少在中創建的字符串的數量,字符串類維護了一個字符串常量池。但是當執行了方法后,將指向字符串常量池中的那個字符串常量。由于和都是字符串常量池中的字面量的引用,所以。究其原因,是因為常量池要保存的是已確定的字面量值。 String,是Java中除了基本數據類型以外,最為重要的一個類型了。很多人會認為他比較簡單。但是和String有關的面試題有很多,下面我隨便找兩道面試題,看看你能不能...

    paulli3 評論0 收藏0
  • 讀書筆記之深入理解Java虛擬

    摘要:前言本文內容基本摘抄自深入理解虛擬機,以供復習之用,沒有多少參考價值。此區域是唯一一個在虛擬機規范中沒有規定任何情況的區域。堆是所有線程共享的內存區域,在虛擬機啟動時創建。虛擬機上把方法區稱為永久代。 前言 本文內容基本摘抄自《深入理解Java虛擬機》,以供復習之用,沒有多少參考價值。想要更詳細了解請參考原書。 第二章 1.運行時數據區域 showImg(https://segment...

    jaysun 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<