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

資訊專欄INFORMATION COLUMN

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

LiuZh / 2728人閱讀

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

1.解釋器與JIT編譯器

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

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

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

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

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

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

2.編譯對象與觸發(fā)條件

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

什么是熱點代碼?

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

被多次調(diào)用的方法。

被多次執(zhí)行的循環(huán)體。

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

3.熱點探測

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

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

基于計數(shù)器的熱點探測:這種方法我們會為每個方法設(shè)置一個計數(shù)器,統(tǒng)計方法被調(diào)用的次數(shù),如果到達一定的次數(shù),我們就把它當(dāng)作是熱點方法

兩種方法的優(yōu)缺點

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

第二種方式的統(tǒng)計結(jié)果會很精確,但需要為每個方法建立并維護一個計數(shù)器。實現(xiàn)上會相對復(fù)雜一點并且開銷也會大點。

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

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

先看一段代碼:

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

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

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

int a = 1;
int b = 2;

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

各種編譯優(yōu)化策略

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

下面我們來講講大概都有那些優(yōu)化策略:

大概預(yù)覽一波:

公共子表達式消除。

數(shù)組范圍檢查消除。

方法內(nèi)聯(lián)。

逃逸分析。

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

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

我們來舉個例子。例如

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

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

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

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

接著繼續(xù)優(yōu)化成

int d = E * 11 + a + a;

接著

int d = E * 11 + 2a;

這樣,代碼在執(zhí)行的時候,就會節(jié)省了一些時間了。

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

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

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

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

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

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

采用優(yōu)化策略之后編程這樣子:

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

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

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

(3).方法內(nèi)聯(lián)

先看一段代碼

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);
}

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

內(nèi)聯(lián)優(yōu)化后的代碼可以是這樣:

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

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

(4).逃逸分析

逃逸分析是目前Java虛擬機比較前沿的優(yōu)化技術(shù),它并非是直接優(yōu)化代碼,而是為其他優(yōu)化手段提供依據(jù)發(fā)分析技術(shù)。

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

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

1).棧上分配

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

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

2).同步消除

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

3).標(biāo)量替換

什么是標(biāo)量?

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

假如這個對象不會發(fā)生逃逸。

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

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

本次講解到這里。

參考書籍:深入Java虛擬機

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

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/76850.html

相關(guān)文章

  • JVM從小白學(xué)成大佬】2.Java虛擬機運行時數(shù)據(jù)區(qū)

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

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

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

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

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

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

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

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

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

    jaysun 評論0 收藏0

發(fā)表評論

0條評論

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