日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

JVM学习-程序编译与优化

發(fā)布時(shí)間:2024/1/16 windows 36 coder
生活随笔 收集整理的這篇文章主要介紹了 JVM学习-程序编译与优化 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文鏈接:https://gaoyubo.cn/blogs/89d6d9be.html

一、前端編譯與優(yōu)化

Java技術(shù)下討論“編譯期”需要結(jié)合具體上下文語(yǔ)境,因?yàn)樗赡艽嬖诤芏喾N情況:

  • 前端編譯器(叫“編譯器的前端”更準(zhǔn)確一些)把.java文件轉(zhuǎn)變成.class文件的過(guò)程

    JDK的Javac、Eclipse JDT中的增量式編譯器(ECJ)

  • 即時(shí)編譯器(常稱JIT編譯器,Just In Time Compiler)運(yùn)行期把字節(jié)碼轉(zhuǎn)變成本地機(jī)器碼的過(guò)程

    HotSpot虛擬機(jī)的C1、C2編譯器,Graal編譯器

  • 提前編譯器(常稱AOT編譯器,Ahead Of Time Compiler)直接把程序編譯成與目標(biāo)機(jī)器指令集相關(guān)的二進(jìn)制代碼的過(guò)程

    JDK的Jaotc、GNU Compiler for the Java(GCJ)、Excelsior JET 。

本章標(biāo)題中的“前端”指的是由前端編譯器完成的編譯行為,對(duì)于前端編譯優(yōu)化,有以下說(shuō)法:

  1. 前端編譯器對(duì)代碼的運(yùn)行效率幾乎沒(méi)有任何優(yōu)化措施可言

  2. Java虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)選擇把對(duì)性能的優(yōu)化全部集中到運(yùn)行期的即時(shí)編譯器

    這樣可以讓那些不是由Javac產(chǎn)生的Class文件也同樣能享受到編譯器優(yōu)化措施所帶來(lái)的性能紅利

  3. 相當(dāng)多新生的Java語(yǔ)法特性,都是靠編譯器的“語(yǔ)法糖”來(lái)實(shí)現(xiàn),而不是依賴字節(jié)碼或者Java虛擬機(jī)的底層改進(jìn)來(lái)支持。

  4. Java中即時(shí)編譯器在運(yùn)行期的優(yōu)化過(guò)程,支撐了程序執(zhí)行效率的不斷提升;

  5. 前端編譯器在編譯期的優(yōu)化過(guò)程,支撐著程序員的編碼效率和語(yǔ)言使用者的幸福感的提高

1.1Javac編譯器

從Javac源代碼的總體結(jié)構(gòu)來(lái)看,編譯過(guò)程大致可以分為1個(gè)準(zhǔn)備過(guò)程和3個(gè)處理過(guò)程,它們分別如下所示:

  1. 準(zhǔn)備過(guò)程:初始化插入式注解處理器

  2. 解析與填充符號(hào)表過(guò)程,包括:

    ? 詞法、語(yǔ)法分析:將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記集合,構(gòu)造出抽象語(yǔ)法樹(shù)

    ? 填充符號(hào)表:產(chǎn)生符號(hào)地址和符號(hào)信息

  3. 插入式注解處理器的注解處理過(guò)程:插入式注解處理器的執(zhí)行階段

  4. 分析與字節(jié)碼生成過(guò)程,包括:

    標(biāo)注檢查:對(duì)語(yǔ)法的靜態(tài)信息進(jìn)行檢查。

    數(shù)據(jù)流及控制流分析:對(duì)程序動(dòng)態(tài)運(yùn)行過(guò)程進(jìn)行檢查。

    解語(yǔ)法糖:將簡(jiǎn)化代碼編寫(xiě)的語(yǔ)法糖還原為原有的形式。

    字節(jié)碼生成:將前面各個(gè)步驟所生成的信息轉(zhuǎn)化成字節(jié)碼。

  5. 對(duì)于以上過(guò)程:執(zhí)行插入式注解時(shí)又可能會(huì)產(chǎn)生新的符號(hào),如果有新的符號(hào)產(chǎn)生,就必須轉(zhuǎn) 回到之前的解析、填充符號(hào)表的過(guò)程中重新處理這些新符號(hào)

  6. 整個(gè)編譯過(guò)程主要的處理由圖中標(biāo)注的8個(gè)方法來(lái)完成

解析和填充符號(hào)表

詞法語(yǔ)法分析

1.詞法分析:詞法分析是將源代碼的字符流轉(zhuǎn)變?yōu)闃?biāo)記(Token)集合的過(guò)程。

2.語(yǔ)法分析:語(yǔ)法分析是根據(jù)標(biāo)記序列構(gòu)造抽象語(yǔ)法樹(shù)的過(guò)程

  • 抽象語(yǔ)法樹(shù):抽象語(yǔ)法樹(shù)(Abstract Syntax Tree,AST)是一 種用來(lái)描述程序代碼語(yǔ)法結(jié)構(gòu)的樹(shù)形表示方式,抽象語(yǔ)法樹(shù)的每一個(gè)節(jié)點(diǎn)都代表著程序代碼中的一個(gè)語(yǔ)法結(jié)構(gòu)

  • 包、類型、修飾符、運(yùn)算符、接口、返回值甚至連代碼注釋等都可以是一種特定的語(yǔ)法結(jié)構(gòu)。

  • 抽象語(yǔ)法樹(shù)可通過(guò)Eclipse AST View插件查看,抽象語(yǔ)法樹(shù)是以com.sun.tools.javac.tree.JCTree 類表示的

  • 經(jīng)過(guò)詞法和語(yǔ)法分析生成語(yǔ)法樹(shù)以后,編譯器就不會(huì)再對(duì)源碼字符流進(jìn)行操作了,后續(xù)的操作都建立在抽象語(yǔ)法樹(shù)之上

填充符號(hào)表

符號(hào)表(Symbol Table)是由一組符號(hào)地址和符號(hào)信息構(gòu)成的數(shù)據(jù)結(jié)構(gòu)(可以理解成哈希表中的鍵值對(duì)的存儲(chǔ)形式)

符號(hào)表中所登記的信息在編譯的不同階段都要被用到:

  • 語(yǔ)義分析的過(guò)程中,符號(hào)表所登記的內(nèi)容將用于語(yǔ)義檢查 (如檢查一個(gè)名字的使用和原先的聲明是否一致)和產(chǎn)生中間代碼
  • 目標(biāo)代碼生成階段,當(dāng)對(duì)符號(hào)名進(jìn)行地址分配時(shí),符號(hào)表是地址分配的直接依據(jù)。

注解處理器

可以把插入式注解處理器看作是一組編譯器的插件,當(dāng)這些插件工作時(shí),允許讀取、修改、添加抽象語(yǔ)法樹(shù)中的任意元素。

譬如Java著名的編碼效率工具Lombok,它可以通過(guò)注解來(lái)實(shí)現(xiàn)自動(dòng)產(chǎn)生 getter/setter方法、進(jìn)行空置檢查、生成受查異常表、產(chǎn)生equals()和hashCode()方法,等等.

語(yǔ)義分析與字節(jié)碼生成

語(yǔ)義分析的主要任務(wù)則是對(duì)結(jié)構(gòu)上正確的源 程序進(jìn)行上下文相關(guān)性質(zhì)的檢查,譬如進(jìn)行類型檢查、控制流檢查、數(shù)據(jù)流檢查,等等

int a = 1;
boolean b = false;
char c = 2;

//后續(xù)可能出現(xiàn)的賦值運(yùn)算:

int d = a + c; 
int d = b + c; //錯(cuò)誤,
char d = a + c; //錯(cuò)誤

//C語(yǔ)言中,a、b、c的上下文定義不變,第二、三種寫(xiě)法都是可以被正確編譯的

我們編碼時(shí)經(jīng)常能在IDE 中看到由紅線標(biāo)注的錯(cuò)誤提示,其中絕大部分都是來(lái)源于語(yǔ)義分析階段的檢查結(jié)果。

1.標(biāo)注檢查

標(biāo)注檢查步驟要檢查的內(nèi)容包括諸如變量使用前是否已被聲明、變量與賦值之間的數(shù)據(jù)類型是否能夠匹配,等等,剛才3個(gè)變量定義的例子就屬于標(biāo)注檢查的處理范疇

在標(biāo)注檢查中,還會(huì)順便進(jìn)行 一個(gè)稱為常量折疊(Constant Folding)的代碼優(yōu)化,這是Javac編譯器會(huì)對(duì)源代碼做的極少量?jī)?yōu)化措施 之一(代碼優(yōu)化幾乎都在即時(shí)編譯器中進(jìn)行)。

int a = 2 + 1;

由于編譯期間進(jìn)行了常量折疊,所以在代碼里面定 義“a=1+2”比起直接定義“a=3”來(lái),并不會(huì)增加程序運(yùn)行期哪怕僅僅一個(gè)處理器時(shí)鐘周期的處理工作量。

2.數(shù)據(jù)及控制流分析

可以檢查出諸如程序局部變量 在使用前是否有賦值、方法的每條路徑是否都有返回值、是否所有的受查異常都被正確處理了等問(wèn)題。

3.解語(yǔ)法糖

在Javac的源碼中,解語(yǔ)法糖的過(guò)程由desugar()方法觸發(fā)。

Java中最常見(jiàn)的語(yǔ)法糖包括了前面提到過(guò)的泛型、變長(zhǎng)參數(shù)、自動(dòng)裝箱拆箱,等等。

4.字節(jié)碼生成

字節(jié)碼生成是Javac編譯過(guò)程的最后一個(gè)階段,在Javac源碼里面由com.sun.tools.javac.jvm.Gen類來(lái)完成。

字節(jié)碼生成階段不僅僅是把前面各個(gè)步驟所生成的信息(語(yǔ)法樹(shù)、符號(hào)表)轉(zhuǎn)化成字節(jié)碼指令寫(xiě)到磁盤中,編譯器還進(jìn)行了少量的代碼添加和轉(zhuǎn)換工作。

實(shí)例構(gòu)造器()方法和類構(gòu)造器()方法就是在這個(gè)階段被添加到語(yǔ) 法樹(shù)之中的

字符串的加操作替換為StringBuffer或StringBuilder(取決于目標(biāo)代碼的版本是否大于或等于JDK 5)的append()操 作,等等。

1.2語(yǔ)法糖的本質(zhì)

泛型

泛型的本質(zhì)是參數(shù)化類型或者參數(shù)化多態(tài)的應(yīng)用,即可以將操作的數(shù)據(jù)類型指定為方法簽名中的一種特殊參數(shù)。

Java選擇的泛型實(shí)現(xiàn)方式叫作類型擦除式泛型:Java語(yǔ)言中的泛型只在程序源碼中存在,在編譯后的字節(jié)碼文件中,全部泛型都被替換為原來(lái)的裸類型了,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼。

類型擦除

裸類型”(Raw Type)的概念:裸類型應(yīng)被視為所有該類型泛型化實(shí)例的共同父類型(Super Type)

ArrayList<Integer> ilist = new ArrayList<Integer>();
ArrayList<String> slist = new ArrayList<String>();
ArrayList list; // 裸類型
list = ilist;
list = slist;

如何實(shí)現(xiàn)裸類型?

直接在編譯時(shí)把ArrayList通過(guò)類型擦除還原回ArrayList,只在元素訪問(wèn)、修改時(shí)自動(dòng)插入一些強(qiáng)制類型轉(zhuǎn)換和檢查指令

泛型擦除前的例子

public static void main(String[] args) {
    Map<String, String> map = new HashMap<String, String>();
    map.put("hello", "你好");
    map.put("how are you?", "吃了沒(méi)?");
    System.out.println(map.get("hello"));
    System.out.println(map.get("how are you?"));
}

把這段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都不見(jiàn)了

public static void main(String[] args) {
    Map map = new HashMap();//裸類型
    map.put("hello", "你好");
    map.put("how are you?", "吃了沒(méi)?");
    System.out.println((String) map.get("hello"));//強(qiáng)制類型轉(zhuǎn)換
    System.out.println((String) map.get("how are you?"));
}

當(dāng)泛型遇到重載

public class GenericTypes {
    public static void method(List<String> list) {
    	System.out.println("invoke method(List<String> list)");
    }
    public static void method(List<Integer> list) {
    	System.out.println("invoke method(List<Integer> list)");
    }
}

參數(shù)列表在特征簽名中,因此參數(shù)列表不同時(shí),可以進(jìn)行重載,但是由于所有泛型都需要通過(guò)類型擦出轉(zhuǎn)化為裸類型,導(dǎo)致參數(shù)都是List list,所以不能重載。會(huì)報(bào)錯(cuò)。

自動(dòng)裝箱、拆箱與遍歷循環(huán)

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3, 4);
    int sum = 0;
    for (int i : list) {
        sum += i;
    }
    System.out.println(sum);
}

編譯后:

public static void main(String[] args) {
    List list = Arrays.asList( new Integer[] {
    Integer.valueOf(1),
    Integer.valueOf(2),
    Integer.valueOf(3),
    Integer.valueOf(4) });
    int sum = 0;
    for (Iterator localIterator = list.iterator(); localIterator.hasNext(); ) {
        int i = ((Integer)localIterator.next()).intValue();
        sum += i;
    }
    System.out.println(sum);
}

二、后端編譯與優(yōu)化

如果把字節(jié)碼看作是程序語(yǔ)言的一種中間表示形式(Intermediate Representation,IR)的話, 那編譯器無(wú)論在何時(shí)、在何種狀態(tài)下把Class文件轉(zhuǎn)換成與本地基礎(chǔ)設(shè)施(硬件指令集、操作系統(tǒng))相關(guān)的二進(jìn)制機(jī)器碼,都可以視為整個(gè)編譯過(guò)程的后端

2.1即時(shí)編譯器

由于即時(shí)編譯器編譯本地代碼需要占用程序運(yùn)行時(shí)間,通常要編譯出優(yōu)化程度越高的代碼,所花 費(fèi)的時(shí)間便會(huì)越長(zhǎng);
而且想要編譯出優(yōu)化程度更高的代碼,解釋器可能還要替編譯器收集性能監(jiān)控信息,這對(duì)解釋執(zhí)行階段的速度也有所影響。
為了在程序啟動(dòng)響應(yīng)速度與運(yùn)行效率之間達(dá)到最佳平衡:

HotSpot虛擬機(jī)在編譯子系統(tǒng)中加入了分層編譯的功能,分層編譯根據(jù)編譯器編譯、優(yōu)化的規(guī)模與耗時(shí),劃分出不同的編譯層次,其中包 括:

  • 第0層。程序純解釋執(zhí)行,并且解釋器不開(kāi)啟性能監(jiān)控功能(Profiling)。
  • 第1層。使用客戶端編譯器將字節(jié)碼編譯為本地代碼來(lái)運(yùn)行,進(jìn)行簡(jiǎn)單可靠的穩(wěn)定優(yōu)化,不開(kāi)啟 性能監(jiān)控功能。
  • 第2層。仍然使用客戶端編譯器執(zhí)行,僅開(kāi)啟方法及回邊次數(shù)統(tǒng)計(jì)等有限的性能監(jiān)控功能。
  • 第3層。仍然使用客戶端編譯器執(zhí)行,開(kāi)啟全部性能監(jiān)控,除了第2層的統(tǒng)計(jì)信息外,還會(huì)收集如分支跳轉(zhuǎn)、虛方法調(diào)用版本等全部的統(tǒng)計(jì)信息。
  • 第4層。使用服務(wù)端編譯器將字節(jié)碼編譯為本地代碼,相比起客戶端編譯器,服務(wù)端編譯器會(huì)啟 用更多編譯耗時(shí)更長(zhǎng)的優(yōu)化,還會(huì)根據(jù)性能監(jiān)控信息進(jìn)行一些不可靠的激進(jìn)優(yōu)化。

編譯對(duì)象與觸發(fā)條件

會(huì)被即時(shí)編譯器編譯的目標(biāo)是熱點(diǎn)代碼,這里所指的熱點(diǎn)代碼主要有兩類:

  • 多次調(diào)用的方法。
  • 多次執(zhí)行的循環(huán)體。

對(duì)于這兩種情況,編譯的目標(biāo)對(duì)象都是整個(gè)方法體,而不會(huì)是單獨(dú)的循環(huán)體。

這種編譯方式因?yàn)?編譯發(fā)生在方法執(zhí)行的過(guò)程中,因此被很形象地稱為棧上替換(On Stack Replacement,OSR),即方法的棧幀還在棧上,方法就被替換了。

多少次才算“多次”呢?

要知道某段代碼是不是熱點(diǎn)代碼,是不是需要觸發(fā)即時(shí)編譯,這個(gè)行為稱為“熱點(diǎn)探測(cè)”(Hot Spot Code Detection),判定方式:

  1. 基于采樣的熱點(diǎn)探測(cè)(Sample Based Hot Spot Code Detection)

    會(huì)周期性地檢查各個(gè)線程的調(diào)用棧頂,如果發(fā)現(xiàn)某個(gè)方法經(jīng)常出現(xiàn)在棧頂,那這個(gè)方法就是熱點(diǎn)方法。

    缺點(diǎn):很難精確地確認(rèn)一個(gè)方法的熱度,容易因?yàn)槭艿骄€程阻塞或別的外界因素的影響而 擾亂熱點(diǎn)探測(cè)

  2. 基于計(jì)數(shù)器的熱點(diǎn)探測(cè)(Counter Based Hot Spot Code Detection)

    為每個(gè)方法(甚至是代碼塊)建立計(jì)數(shù)器,統(tǒng)計(jì)方法的執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過(guò)一定的閾值就認(rèn)為它是熱點(diǎn)方法。

    缺點(diǎn):實(shí)現(xiàn)起來(lái)要麻煩一些,需要為每個(gè)方法建立并維護(hù)計(jì)數(shù)器,而且不能 直接獲取到方法的調(diào)用關(guān)系

J9用過(guò)第一種采樣熱點(diǎn)探測(cè),而在HotSpot 虛擬機(jī)中使用的是第二種基于計(jì)數(shù)器的熱點(diǎn)探測(cè)方法,

HotSpot 中每個(gè)方法的 2 個(gè)計(jì)數(shù)器

  • 方法調(diào)用計(jì)數(shù)器
    • 統(tǒng)計(jì)方法被調(diào)用的次數(shù),處理多次調(diào)用的方法的。
    • 默認(rèn)統(tǒng)計(jì)的不是方法調(diào)用的絕對(duì)次數(shù),而是方法在一段時(shí)間內(nèi)被調(diào)用的次數(shù),如果超過(guò)這個(gè)時(shí)間限制還沒(méi)有達(dá)到判為熱點(diǎn)代碼的閾值,則該方法的調(diào)用計(jì)數(shù)器值減半。
      • 關(guān)閉熱度衰減:-XX: -UseCounterDecay(此時(shí)方法計(jì)數(shù)器統(tǒng)計(jì)的是方法被調(diào)用的絕對(duì)次數(shù));
      • 設(shè)置半衰期時(shí)間:-XX: CounterHalfLifeTime(單位是秒);
      • 熱度衰減過(guò)程是在 GC 時(shí)順便進(jìn)行。
      • 默認(rèn)閾值在客戶端模式下是1500次,在服務(wù)端模式下是10000次,
  • 回邊計(jì)數(shù)器
    • 統(tǒng)計(jì)一個(gè)方法中 “回邊” 的次數(shù),處理多次執(zhí)行的循環(huán)體的。
      • 回邊:在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令(不是所有循環(huán)體都是回邊,空循環(huán)體是自己跳向自己,沒(méi)有向后跳,不算回邊)。
    • 調(diào)整回邊計(jì)數(shù)器閾值:-XX: OnStackReplacePercentage(OSR比率)
      • Client 模式:回邊計(jì)數(shù)器的閾值 = 方法調(diào)用計(jì)數(shù)器閾值 * OSR比率 / 100;
      • Server 模式:回邊計(jì)數(shù)器的閾值 = 方法調(diào)用計(jì)數(shù)器閾值 * ( OSR比率 - 解釋器監(jiān)控比率 ) / 100;

編譯過(guò)程

虛擬機(jī)在代碼編譯未完成時(shí)會(huì)按照解釋方式繼續(xù)執(zhí)行,編譯動(dòng)作在后臺(tái)的編譯線程執(zhí)行。

禁止后臺(tái)編譯:-XX: -BackgroundCompilation,打開(kāi)后這個(gè)開(kāi)關(guān)參數(shù)后,交編譯請(qǐng)求的線程會(huì)等待編譯完成,然后執(zhí)行編譯器輸出的本地代碼。

在后臺(tái)編譯過(guò)程中,客戶端編譯器與服務(wù)端編譯器是有區(qū)別的。

客戶端編譯器

是一個(gè)相對(duì)簡(jiǎn)單快速的三段式編譯器,主要的關(guān)注點(diǎn)在于局部性的優(yōu)化,而放棄了許多耗時(shí)較長(zhǎng)的全局優(yōu)化手段。

  1. 第一個(gè)階段,一個(gè)平*立的前端將字節(jié)碼構(gòu)造成一種高級(jí)中間代碼表示(High-Level Intermediate Representation,HIR,即與目標(biāo)機(jī)器指令集無(wú)關(guān)的中間表示)。HIR使用靜態(tài)單分配 (Static Single Assignment,SSA)的形式來(lái)代表代碼值,這可以使得一些在HIR的構(gòu)造過(guò)程之中和之后進(jìn)行的優(yōu)化動(dòng)作更容易實(shí)現(xiàn)。在此之前編譯器已經(jīng)會(huì)在字節(jié)碼上完成一部分基礎(chǔ)優(yōu)化,如方法內(nèi)聯(lián)、 常量傳播等優(yōu)化將會(huì)在字節(jié)碼被構(gòu)造成HIR之前完成。

  2. 第二個(gè)階段,一個(gè)平臺(tái)相關(guān)的后端從HIR中產(chǎn)生低級(jí)中間代碼表示(Low-Level Intermediate Representation,LIR,即與目標(biāo)機(jī)器指令集相關(guān)的中間表示),而在此之前會(huì)在HIR上完成另外一些優(yōu)化,如空值檢查消除、范圍檢查消除等,以便讓HIR達(dá)到更高效的代碼表示形式。

  3. 最后的階段是在平臺(tái)相關(guān)的后端使用線性掃描算法(Linear Scan Register Allocation)在LIR上分配寄存器,并在LIR上做窺孔(Peephole)優(yōu)化,然后產(chǎn)生機(jī)器代碼??蛻舳司幾g器大致的執(zhí)行過(guò)程如圖

服務(wù)端編譯器

是專門面向服務(wù)端的典型應(yīng)用場(chǎng)景,執(zhí)行大部分經(jīng)典的優(yōu)化動(dòng)作,如:無(wú)用代碼消除(Dead Code Elimination)、循環(huán)展開(kāi) (Loop Unrolling)、循環(huán)表達(dá)式外提(Loop Expression Hoisting)、消除公共子表達(dá)式(Common Subexpression Elimination)、常量傳播(Constant Propagation)、基本塊重排序(Basic Block Reordering)等,還會(huì)實(shí)施一些與Java語(yǔ)言特性密切相關(guān)的優(yōu)化技術(shù),如范圍檢查消除(Range Check Elimination)、空值檢查消除(Null Check Elimination,不過(guò)并非所有的空值檢查消除都是依賴編譯器優(yōu)化的,有一些是代碼運(yùn)行過(guò)程中自動(dòng)優(yōu)化了)等。

另外,還可能根據(jù)解釋器或客戶端編譯器提供的 性能監(jiān)控信息,進(jìn)行一些不穩(wěn)定的預(yù)測(cè)性激進(jìn)優(yōu)化,如守護(hù)內(nèi)聯(lián)(Guarded Inlining)、分支頻率預(yù)測(cè) (Branch Frequency Prediction)等

服務(wù)端編譯采用的寄存器分配器是一個(gè)全局圖著色分配器,它可以充分利用某些處理器架構(gòu)(如 RISC)上的大寄存器集合。

2.2提前編譯器

現(xiàn)在提前編譯產(chǎn)品和對(duì)其的研究有著兩條明顯的分支:

  1. 與傳統(tǒng)C、C++編譯器類似的,在程序運(yùn)行之前把程序代碼編譯成機(jī)器碼的靜態(tài)翻譯工作

  2. 把原本即時(shí)編譯器在運(yùn)行時(shí)要做的編譯工作提前做好并保存下來(lái),下次運(yùn)行到這些代碼(譬如公共庫(kù)代碼在被同一臺(tái)機(jī)器其他Java進(jìn)程使用)時(shí)直接把它加載進(jìn)來(lái)使用。(本質(zhì)是給即時(shí)編譯器做緩存加速,去改善Java程序的啟動(dòng)時(shí)間)

    在目前的Java技術(shù)體系里,這種提前編譯已經(jīng)完全被主流的商用JDK支持

    困難:這種提前編譯方式不僅要和目標(biāo)機(jī)器相關(guān),甚至還必須與HotSpot虛擬機(jī)的運(yùn)行時(shí)參數(shù)綁定(如生成內(nèi)存屏障代碼)才能正確工作,要做提前編譯的話,自然也要把這些配合的工作平移過(guò)去。

2.3即時(shí)編譯器的優(yōu)勢(shì)

提前編譯的代碼輸出質(zhì)量,一定會(huì)比即時(shí)編譯更高嗎?

以下為即時(shí)編譯器相較于提前編譯器的優(yōu)勢(shì):

  1. 性能分析制導(dǎo)優(yōu)化(Profile-Guided Optimization,PGO)

    抽象類通常會(huì)是什么實(shí)際類型、條件判斷通常會(huì)走哪條分支、方法調(diào)用通常會(huì)選擇哪個(gè)版本、循環(huán)通常會(huì)進(jìn)行多少次等,這些數(shù)據(jù)一般在靜態(tài)分析時(shí)是無(wú)法得到的,或者不可能存在確定且唯一的解, 最多只能依照一些啟發(fā)性的條件去進(jìn)行猜測(cè)。但在動(dòng)態(tài)運(yùn)行時(shí)卻能看出它們具有非常明顯的偏好性。就可以把熱的代碼集中放到 一起,集中優(yōu)化和分配更好的資源(分支預(yù)測(cè)、寄存器、緩存等)給它。

  2. 激進(jìn)預(yù)測(cè)性優(yōu)化(Aggressive Speculative Optimization)

    靜態(tài)優(yōu)化無(wú)論如何都必須保證優(yōu)化后所有的程序外部可見(jiàn)影響(不僅僅是執(zhí)行結(jié)果) 與優(yōu)化前是等效的

    然而,即時(shí)編譯的策略就可以不必這樣保守,可以大膽地按照高概率的假設(shè)進(jìn)行優(yōu)化,萬(wàn)一真的走到罕見(jiàn)分支上,大不了退回到低級(jí)編譯器甚至解釋器上去執(zhí)行,并不會(huì)出現(xiàn)無(wú)法挽救的后果。

    如果Java虛擬機(jī)真的遇到虛方法就去查虛表而不做內(nèi) 聯(lián)的話,Java技術(shù)可能就已經(jīng)因性能問(wèn)題而被淘汰很多年了。

    實(shí)際上虛擬機(jī)會(huì)通過(guò)類繼承關(guān)系分析等 一系列激進(jìn)的猜測(cè)去做去虛擬化(Devitalization),以保證絕大部分有內(nèi)聯(lián)價(jià)值的虛方法都可以順利內(nèi)聯(lián)。

  3. 鏈接時(shí)優(yōu)化(Link-Time Optimization,LTO)

    如C、C++的程序要調(diào)用某個(gè)動(dòng)態(tài)鏈接庫(kù)的某個(gè)方法,就會(huì)出現(xiàn)很明顯的邊界隔閡,還難以優(yōu)化。
    這是因?yàn)橹鞒绦蚺c動(dòng)態(tài)鏈接庫(kù)的代碼在它們編譯時(shí)是完全獨(dú)立的,兩者各自編譯、優(yōu)化自己的代碼。

    然而,Java語(yǔ)言天生就是動(dòng)態(tài)鏈接的,一個(gè)個(gè) Class文件在運(yùn)行期被加載到虛擬機(jī)內(nèi)存當(dāng)中。

三、編譯器優(yōu)化技術(shù)

類型 優(yōu)化技術(shù)
編譯器策略 (Compiler Tactics)
延遲編譯 (Delayed Compilation)
分層編譯 (Tiered Compilation)
棧上替換 (On-Stack Replacement)
延遲優(yōu)化 (Delayed Reoptimization)
靜態(tài)單賦值表示 (Static Single Assignment Representation)
基于性能監(jiān)控的優(yōu)化技術(shù) (Profile-Based Techniques)
樂(lè)觀空值斷言 (Optimistic Nullness Assertions)
樂(lè)觀類型斷言 (Optimistic Type Assertions)
樂(lè)觀類型增強(qiáng) (Optimistic Type Strengthening)
樂(lè)觀數(shù)組長(zhǎng)度增強(qiáng) (Optimistic Array Length Strengthening)
裁剪未被選擇的分支 (Untaken Branch Pruning)
樂(lè)觀的多態(tài)內(nèi)聯(lián) (Optimistic N-morphic Inlining)
分支頻率預(yù)測(cè) (Branch Frequency Prediction)
調(diào)用頻率預(yù)測(cè) (Call Frequency Prediction)
基于證據(jù)的優(yōu)化技術(shù) (Proof-Based Techniques)
精確類型推斷 (Exact Type Inference)
內(nèi)存值推斷 (Memory Value Inference)
內(nèi)存值跟蹤 (Memory Value Tracking)
常量折疊 (Constant Folding)
重組 (Reassociation)
操作符退化 (Operator Strength Reduction)
空值檢查消除 (Null Check Elimination)
類型檢測(cè)退化 (Type Test Strength Reduction)
類型檢測(cè)消除 (Type Test Elimination)
代數(shù)簡(jiǎn)化 (Algebraic Simplification)
公共子表達(dá)式消除 (Common Subexpression Elimination)
數(shù)據(jù)流敏感重寫(xiě) (Flow-Sensitive Rewrites)
條件常量傳播 (Conditional Constant Propagation)
基于流承載的類型縮減轉(zhuǎn)換 (Flow-Carried Type Narrowing)
無(wú)用代碼消除 (Dead Code Elimination)
語(yǔ)言相關(guān)的優(yōu)化技術(shù) (Language-Specific Techniques)
類型繼承關(guān)系分析 (Class Hierarchy Analysis)
去虛擬化 (Devirtualization)
符號(hào)常量傳播 (Symbolic Constant Propagation)
自動(dòng)裝箱消除 (Autobox Elimination)
逃逸分析 (Escape Analysis)
鎖消除 (Lock Elision)
鎖膨脹 (Lock Coarsening)
消除反射 (De-reflection)
內(nèi)存及代碼位置變換 (Memory and Placement Transformation)
表達(dá)式提升 (Expression Hoisting)
表達(dá)式下沉 (Expression Sinking)
冗余存儲(chǔ)消除 (Redundant Store Elimination)
相鄰存儲(chǔ)合并 (Adjacent Store Fusion)
交匯點(diǎn)分離 (Merge-Point Splitting)
循環(huán)變換 (Loop Transformations)
循環(huán)展開(kāi) (Loop Unrolling)
循環(huán)剝離 (Loop Peeling)
安全點(diǎn)消除 (Safepoint Elimination)
迭代范圍分離 (Iteration Range Splitting)
范圍檢查消除 (Range Check Elimination)
循環(huán)向量化 (Loop Vectorization)
全局代碼調(diào)整 (Global Code Shaping)
內(nèi)聯(lián) (Inlining)
全局代碼外提 (Global Code Motion)
基于熱度的代碼布局 (Heat-Based Code Layout)
Switch調(diào)整 (Switch Balancing)
控制流圖變換 (Control Flow Graph Transformation)
本地代碼編排 (Local Code Scheduling)
本地代碼封包 (Local Code Bundling)
延遲槽填充 (Delay Slot Filling)
著色圖寄存器分配 (Graph-Coloring Register Allocation)
線性掃描寄存器分配 (Linear Scan Register Allocation)
復(fù)寫(xiě)聚合 (Copy Coalescing)
常量分裂 (Constant Splitting)
復(fù)寫(xiě)移除 (Copy Removal)
地址模式匹配 (Address Mode Matching)
指令窺空優(yōu)化 (Instruction Peepholing)
基于確定有限狀態(tài)機(jī)的代碼生成 (DFA-Based Code Generator)

3.1一個(gè)優(yōu)化的例子

原始代碼:

static class B {
    int value;
    final int get() {
        return value;
    }
}

public void foo() {
    y = b.get();
    // ...do stuff...
    z = b.get();
    sum = y + z;
}

第一步優(yōu)化: 方法內(nèi)聯(lián)(一般放在優(yōu)化序列最前端,因?yàn)閷?duì)其他優(yōu)化有幫助)

目的:

  • 去除方法調(diào)用的成本(如建立棧幀等)
  • 為其他優(yōu)化建立良好的基礎(chǔ)
public void foo() {
    y = b.value;
    // ...do stuff...
    z = b.value;
    sum = y + z;
}

第二步優(yōu)化: 公共子表達(dá)式消除

public void foo() {
    y = b.value;
    // ...do stuff...  // 因?yàn)檫@部分并沒(méi)有改變 b.value 的值
                       // 如果把 b.value 看成一個(gè)表達(dá)式,就是公共表達(dá)式消除
    z = y;             // 把這一步的 b.value 替換成 y
    sum = y + z;
}

第三步優(yōu)化: 復(fù)寫(xiě)傳播

public void foo() {
    y = b.value;
    // ...do stuff...
    y = y;             // z 變量與以相同,完全沒(méi)有必要使用一個(gè)新的額外變量
                       // 所以將 z 替換為 y
    sum = y + z;
}

第四步優(yōu)化: 無(wú)用代碼消除

無(wú)用代碼:

  • 永遠(yuǎn)不會(huì)執(zhí)行的代碼
  • 完全沒(méi)有意義的代碼
public void foo() {
    y = b.value;
    // ...do stuff...
    // y = y; 這句沒(méi)有意義的,去除
    sum = y + y;
}

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

它是編譯器最重要的優(yōu)化手段,甚至都可以不加 上“之一”。

除了消除方法調(diào)用的成本之外,它更重要的意義是為其他優(yōu)化手段建立良好的基礎(chǔ)

目的是:去除方法調(diào)用的成本(如建立棧幀等),并為其他優(yōu)化建立良好的基礎(chǔ),所以一般將方法內(nèi)聯(lián)放在優(yōu)化序列最前端,因?yàn)樗鼘?duì)其他優(yōu)化有幫助。

為了解決虛方法的內(nèi)聯(lián)問(wèn)題:引入類型繼承關(guān)系分析(Class Hierarchy Analysis,CHA)

用于確定在目前已加載的類中,某個(gè)接口是否有多于一種的實(shí)現(xiàn),某個(gè)類是否存在子類、子類是否為抽象類等。

  • 對(duì)于非虛方法:
    • 直接進(jìn)行內(nèi)聯(lián),其調(diào)用方法的版本在編譯時(shí)已經(jīng)確定,是根據(jù)變量的靜態(tài)類型決定的。
  • 對(duì)于虛方法: (激進(jìn)優(yōu)化,要預(yù)留“逃生門”)
    • 向 CHA 查詢此方法在當(dāng)前程序下是否有多個(gè)目標(biāo)可選擇;
      • 只有一個(gè)目標(biāo)版本:
        • 先對(duì)這唯一的目標(biāo)進(jìn)行內(nèi)聯(lián);
        • 如果之后的執(zhí)行中,虛擬機(jī)沒(méi)有加載到會(huì)令這個(gè)方法接收者的繼承關(guān)系發(fā)生改變的新類,則該內(nèi)聯(lián)代碼可以一直使用;
        • 如果加載到導(dǎo)致繼承關(guān)系發(fā)生變化的新類,就拋棄已編譯的代碼,退回到解釋狀態(tài)進(jìn)行執(zhí)行,或者重新進(jìn)行編譯。
      • 有多個(gè)目標(biāo)版本:
        • 使用內(nèi)聯(lián)緩存,未發(fā)生方法調(diào)用前,內(nèi)聯(lián)緩存為空;
        • 第一次調(diào)用發(fā)生后,記錄調(diào)用方法的對(duì)象的版本信息;
        • 之后的每次調(diào)用都要先與內(nèi)聯(lián)緩存中的對(duì)象版本信息進(jìn)行比較;
          • 版本信息一樣,繼續(xù)使用內(nèi)聯(lián)代碼,是一種單態(tài)內(nèi)聯(lián)緩存(Monomorphic Inline Cache)
          • 版本信息不一樣,說(shuō)明程序使用了虛方法的多態(tài)特性,退化成超多態(tài)內(nèi)聯(lián)緩存(Megamorphic Inline Cache),查找虛方法進(jìn)行方法分派。

3.3逃逸分析【最前沿】

基本行為

分析對(duì)象的作用域,看它有沒(méi)有能在當(dāng)前作用域之外使用:

  • 方法逃逸:對(duì)象在方法中定義之后,能被外部方法引用,如作為參數(shù)傳遞到了其他方法中。
  • 線程逃逸:賦值給 static 變量,或可以在其他線程中訪問(wèn)的實(shí)例變量。

對(duì)于不會(huì)逃逸到方法或線程外的對(duì)象能進(jìn)行優(yōu)化

  • 棧上分配: 對(duì)于不會(huì)逃逸到方法外的對(duì)象,可以在棧上分配內(nèi)存,這樣這個(gè)對(duì)象所占用的空間可以隨棧幀出棧而銷毀,減小 GC 的壓力。
  • 標(biāo)量替換(重要):
    • 標(biāo)量:基本數(shù)據(jù)類型和 reference。
    • 不創(chuàng)建對(duì)象,而是將對(duì)象拆分成一個(gè)一個(gè)標(biāo)量,然后直接在棧上分配,是棧上分配的一種實(shí)現(xiàn)方式。
    • HotSpot 使用的是標(biāo)量替換而不是棧上分配,因?yàn)閷?shí)現(xiàn)棧上分配需要更改大量假設(shè)了 “對(duì)象只能在堆中分配” 的代碼。
  • 同步消除
    • 如果逃逸分析能夠確定一個(gè)變量不會(huì)逃逸出線程,無(wú)法被其他線程訪問(wèn),對(duì)這個(gè)變量實(shí)施的同步措施也就可以安全地消除掉。

虛擬機(jī)參數(shù)

  • 開(kāi)啟逃逸分析:-XX: +DoEscapeAnalysis
  • 開(kāi)啟標(biāo)量替換:-XX: +EliminateAnalysis
  • 開(kāi)啟鎖消除:-XX: +EliminateLocks
  • 查看分析結(jié)果:-XX: PrintEscapeAnalysis
  • 查看標(biāo)量替換情況:-XX: PrintEliminateAllocations

例子

Point類的代碼,這就是一個(gè)包含x和y坐標(biāo)的POJO類型

// 完全未優(yōu)化的代碼
public int test(int x) {
    int xx = x + 2;
    Point p = new Point(xx, 42);
    return p.getX();
}

步驟1:構(gòu)造函數(shù)內(nèi)聯(lián)

public int test(int x) {
    int xx = x + 2;
    Point p = point_memory_alloc(); // 在堆中分配P對(duì)象的示意方法
    p.x = xx; // Point構(gòu)造函數(shù)被內(nèi)聯(lián)后的樣子
    p.y = 42;
    return p.x; // Point::getX()被內(nèi)聯(lián)后的樣子
}

步驟2:標(biāo)量替換

經(jīng)過(guò)逃逸分析,發(fā)現(xiàn)在整個(gè)test()方法的范圍內(nèi)Point對(duì)象實(shí)例不會(huì)發(fā)生任何程度的逃逸, 這樣可以對(duì)它進(jìn)行標(biāo)量替換優(yōu)化,把其內(nèi)部的x和y直接置換出來(lái),分解為test()方法內(nèi)的局部變量,從 而避免Point對(duì)象實(shí)例被實(shí)際創(chuàng)建

public int test(int x) {
    int xx = x + 2;
    int px = xx;
    int py = 42;
    return px;
}

步驟3:無(wú)效代碼消除

通過(guò)數(shù)據(jù)流分析,發(fā)現(xiàn)py的值其實(shí)對(duì)方法不會(huì)造成任何影響,那就可以放心地去做無(wú)效代碼消除得到最終優(yōu)化結(jié)果,

public int test(int x) {
    return x + 2;
}

總結(jié)

以上是生活随笔為你收集整理的JVM学习-程序编译与优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 日韩1区2区3区 | 一本色道久久综合亚洲精品 | 免费观看av网址 | 激情综合六月 | 欧美在线一二 | 欧美xxxxx视频 | 久久超碰精品 | 色a在线| 深夜福利一区二区 | 波多野结衣影院 | 亚洲一区无 | 欧美日韩在线一区二区 | 成人免费视频一区二区 | 欧美日韩在线播放视频 | 日本福利社 | 一区二区国产在线观看 | 91av久久| 欧美性生活| 欧美天堂在线观看 | 久久精品一区二区三区黑人印度 | 午夜中出| 久久久久久影院 | 自拍偷拍2019| 一本大道东京热无码aⅴ | 国产精品36p | 国产一级二级在线 | 国产精品91av | 一区不卡在线 | 亚洲一区免费电影 | 天堂影视在线观看 | 久久一级片 | а√天堂中文在线资源8 | 日韩欧美亚洲一区二区三区 | 色戒电影未测减除版 | 精品视频久久久久久 | 黑人精品一区二区三区 | 日韩精品三级 | www.一区二区三区四区 | 香蕉网在线观看 | 黄色小视频在线看 | 成人美女毛片 | 日韩欧美国产成人 | aaaaa级片 | 91桃色视频在线观看 | 很污很黄的网站 | 欧美黑人性生活 | 老女人做爰全过程免费的视频 | 国产精品 欧美 日韩 | 天堂视频在线免费观看 | 国产欧美日韩视频在线观看 | 99久久精品国产毛片 | 国产chinese中国hdxxxx | 在线亚洲网站 | 综合色小说 | 麻豆视频网页 | 中文字幕在线观看网站 | 久久伊人操 | 熟妇人妻va精品中文字幕 | 国产乱码精品一区二三赶尸艳谈 | 超碰操| 91久久亚洲| 中文精品久久 | 日本一级淫片1000部 | 亚洲九区| 老女人做爰全过程免费的视频 | 亚洲av人人夜夜澡人人 | 亚洲 欧美 国产 另类 | 久久aaaa片一区二区 | 中文字幕av一区二区三区谷原希美 | 一区二区三区观看 | 好男人影视www | 少妇无码吹潮 | 99热这里只有精品5 国产精品伦子伦免费视频 精品一二三 | 欧美视频三区 | www男人的天堂 | wwwxxoo| 男男受被啪到高潮自述 | 色老头综合网 | 国v精品久久久网 | 色呦呦麻豆 | 久草热在线视频 | 日本国产在线观看 | 51调教丨国产调教视频 | 好吊色在线视频 | 久久噜噜噜精品国产亚洲综合 | 久久久久久欧美精品se一二三四 | 最新国产黄色网址 | 男女啊啊啊 | 日韩精品在线观看一区二区 | 一区二区三区在线免费观看视频 | 亚洲视频精选 | 欧美亚洲高清 | 青草伊人网 | 亚洲逼| 美国伊人网 | 在线不卡国产 | 亚洲最大中文字幕 | 国产乱码一区二区 | 国产xxx视频 |