精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

探究 Java 應用的啟動速度優化

網絡
在高性能的背后,Java 的啟動性能差也令人印象深刻,大家印象中的 Java 笨重緩慢的印象也大多來源于此。高性能和快啟動速度似乎有一些相悖,本文將和大家一起探究兩者是否可以兼得。

[[418030]]

一 高性能和快啟動速度,能否魚和熊掌兼得?

Java 作為一門面向對象編程語言,在性能方面的卓越表現獨樹一幟。

《Energy Efficiency across Programming Languages,How Does Energy, Time, and Memory Relate?》這份報告調研了各大編程語言的執行效率,雖然場景的豐富程度有限,但是也能夠讓我們見微知著。

從表中,我們可以看到,Java 的執行效率非常高,約為最快的C語言的一半。這在主流的編程語言中,僅次于C、Rust 和 C++。

Java 的優異性能得益于 Hotspot 中非常優秀的 JIT 編譯器。Java 的 Server Compiler(C2) 編譯器是 Cliff Click 博士的作品,使用了 Sea-of-Nodes 模型。而這項技術,也通過時間證明了它代表了業界的最先進水平:

著名的V8(JavaScript引擎)的 TurboFan 編譯器使用了相同的設計,只是用更加現代的方式去實現;
Hotspot 使用 Graal JVMCI 做 JIT 時,性能基本與 C2 持平;
Azul 的商業化產品將 Hotspot 中的 C2 compiler 替換成 LLVM,峰值性能和 C2 也是持平。
在高性能的背后,Java 的啟動性能差也令人印象深刻,大家印象中的 Java 笨重緩慢的印象也大多來源于此。高性能和快啟動速度似乎有一些相悖,本文將和大家一起探究兩者是否可以兼得。

二 Java 啟動慢的根因

1 框架復雜

JakartaEE 是 Oracle 將 J2EE 捐贈給 Eclipse 基金會后的新名字。Java 在1999年推出時便發布了 J2EE 規范,EJB(Java Enterprise Beans) 定義了企業級開發所需要的安全、IoC、AOP、事務、并發等能力。設計極度復雜,最基本的應用都需要大量的配置文件,使用非常不便。

隨著互聯網的興起,EJB 逐漸被更加輕量和免費的 Spring 框架取代,Spring 成了 Java 企業開發的事實標準。Spring 雖然定位更加輕量,但是骨子里依然很大程度地受 JakartaEE 的影響,比如早期版本大量 xml 配置的使用、大量 JakartaEE 相關的注解(比如JSR 330依賴注入),以及規范(如JSR 340 Servlet API)的使用。

但 Spring 仍是一個企業級的框架,我們看幾個 Spring 框架的設計哲學:

在每一層都提供選項,Spring 可以讓你盡可能的推遲選擇。
適應不同的視角,Spring 具有靈活性,它不會強制為你決定該怎么選擇。它以不同的視角支持廣泛的應用需求。
保持強大的向后兼容性。
在這種設計哲學的影響下,必然存在大量的可配置和初始化邏輯,以及復雜的設計模式來支撐這種靈活性。我們通過一個試驗來看:

我們跑一個spring-boot-web的helloword,通過-verbose:class可以看到依賴的class文件:

  1. $ java -verbose:class -jar myapp-1.0-SNAPSHOT.jar | grep spring | head -n 5[Loaded org.springframework.boot.loader.Launcher from file:/Users/yulei/tmp/myapp-1.0-SNAPSHOT.jar][Loaded org.springframework.boot.loader.ExecutableArchiveLauncher from file:/Users/yulei/tmp/myapp-1.0-SNAPSHOT.jar][Loaded org.springframework.boot.loader.JarLauncher from file:/Users/yulei/tmp/myapp-1.0-SNAPSHOT.jar][Loaded org.springframework.boot.loader.archive.Archive from file:/Users/yulei/tmp/myapp-1.0-SNAPSHOT.jar][Loaded org.springframework.boot.loader.LaunchedURLClassLoader from file:/Users/yulei/tmp/myapp-1.0-SNAPSHOT.jar]$ java -verbose:class -jar myapp-1.0-SNAPSHOT.jar | egrep '^\[Loaded' > classes$ wc classes    7404   29638 1175552 classes 

class 個數到達驚人的7404個。

我們再對比下 JavaScript 生態,使用常用的 express 編寫一個基本應用:

  1. const express = require('express')const app = express()app.get('/', (req, res) => {  res.send('Hello World!')})    app.listen(3000, () => {    console.log(`Example app listening at http://localhost:${port}`)}) 

我們借用 Node 的 debug 環境變量分析:

  1. NODE_DEBUG=module node app.js 2>&1  | head -n 5MODULE 18614: looking for "/Users/yulei/tmp/myapp/app.js" in ["/Users/yulei/.node_modules","/Users/yulei/.node_libraries","/usr/local/Cellar/node/14.4.0/lib/node"]MODULE 18614: load "/Users/yulei/tmp/myapp/app.js" for module "."MODULE 18614: Module._load REQUEST express parent: .MODULE 18614: looking for "express" in ["/Users/yulei/tmp/myapp/node_modules","/Users/yulei/tmp/node_modules","/Users/yulei/node_modules","/Users/node_modules","/node_modules","/Users/yulei/.node_modules","/Users/yulei/.node_libraries","/usr/local/Cellar/node/14.4.0/lib/node"]MODULE 18614: load "/Users/yulei/tmp/myapp/node_modules/express/index.js" for module "/Users/yulei/tmp/myapp/node_modules/express/index.js"$ NODE_DEBUG=module node app.js 2>&1  | grep ': load "' > js$ wc js      55     392    8192 js 

這里只依賴了區區55個 js 文件。

雖然拿 spring-boot 和 express 比并不公平。在 Java 世界也可以基于 Vert.X、Netty 等更加輕量的框架來構建應用,但是在實踐中,大家幾乎都會不假思索地選擇 spring-boot,以便享受 Java 開源生態的便利。

2 一次編譯,到處運行

Java 啟動慢是因為框架復雜嗎?答案只能說框架復雜是啟動慢的原因之一。通過 GraalVM 的 Native Image 功能結合 spring-native 特性,可以將 spring-boot 應用的啟動時間縮短約十倍。

Java 的 Slogan 是 "Write once, run anywhere"(WORA),Java 也確實通過字節碼和虛擬機技術做到了這一點。

WORA 使得開發者在 MacOS 上開發調試完成的應用可以快速部署到 Linux 服務器,跨平臺性也讓 Maven 中心倉庫更加易于維護,促成了 Java 開源生態的繁榮。

我們來看一下 WORA 對 Java 的影響:

Class Loading
Java 通過 class 來組織源碼,class 被塞進 JAR 包以便組織成模塊和分發,JAR 包本質上是一個 ZIP 文件:

  1. $ jar tf slf4j-api-1.7.25.jar | headMETA-INF/META-INF/MANIFEST.MForg/slf4j/org/slf4j/event/EventConstants.classorg/slf4j/event/EventRecodingLogger.classorg/slf4j/event/Level.class 

每個 JAR 包都是功能上比較獨立的模塊,開發者就可以按需依賴特定功能的 JAR,這些 JAR 通過 class path 被JVM 所知悉,并進行加載。

根據,執行到 new 或者 invokestatic 字節碼時會觸發類加載。JVM 會將控制交給 Classloader ,最常見的實現 URLClassloader 會遍歷 JAR 包,去尋找相應的 class 文件:

  1. for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) {    Resource res = loader.getResource(name, check);    if (res != null) {        return res;    }} 

因此查找類的開銷,通常和 JAR 包個數成正比,在大型應用的場景下個數會上千,導致整體的查找耗時很高。

當找到 class 文件后 JVM 需要校驗 class 文件的是否合法,并解析成內部可用的數據結構,在 JVM 中叫做 InstanceKlass ,聽過 javap 窺視一下class文件包含的信息:

  1. $ javap -p SimpleMessage.classpublic class org.apache.logging.log4j.message.SimpleMessage implements org.apache.logging.log4j.message.Message,org.apache.logging.log4j.util.StringBuilderFormattable,java.lang.CharSequence {  private static final long serialVersionUID;  private java.lang.String message;  private transient java.lang.CharSequence charSequence;  public org.apache.logging.log4j.message.SimpleMessage();  public org.apache.logging.log4j.message.SimpleMessage(java.lang.String); 

這個結構包含接口、基類、靜態數據、對象的 layout、方法字節碼、常量池等等。這些數據結構都是解釋器執行字節碼或者JIT編譯所必須的。

Class initialize

當類被加載完成后,要完成初始化才能實際創建對象或者調用靜態方法。類初始化可以簡單理解為靜態塊:

  1. public class A {  private final static String JAVA_VERSION_STRING = System.getProperty("java.version");    private final static Set<Integer> idBlackList = new HashSet<>();    static {        idBlackList.add(10);        idBlackList.add(65538);    }} 

上面的第一個靜態變量 JAVA_VERSION_STRING 的初始化在編譯成字節碼后也會成為靜態塊的一部分。

類初始化有如下特點:

只執行一次;
有多線程嘗試訪問類時,只有一個線程會執行類初始化,JVM 保證其他線程都會阻塞等待初始化完成。
這些特點非常適合讀取配置,或者構造一些運行時所需要數據結構、緩存等等,因此很多類的初始化邏輯會寫的比較復雜。

Just In Time compile
Java 類在被初始化后就可以實例對象,并調用對象上的方法了。解釋執行類似一個大的 switch..case 循環,性能比較差:

  1. while (true) {  switch(bytocode[pc]) {        case AALOAD:            ...            break;        case ATHROW:            ...            break;    }} 

我們用 JMH 來跑一個 Hessian 序列化的 Micro Benchmark 試驗:

  1. $ java -jar benchmarks.jar hessianIOBenchmark                      Mode  Cnt       Score   Error  UnitsSerializeBenchmark.hessianIO  thrpt       118194.452          ops/s$ java -Xint -jar benchmarks.jar hessianIOBenchmark                      Mode  Cnt     Score   Error  UnitsSerializeBenchmark.hessianIO  thrpt       4535.820          ops/s 

第二次運行的 -Xint 參數控制了我們只使用解釋器,這里差了26倍,這是直接機器執行的執行和解釋執行的差異帶來的。這個差距跟場景的關系很大,我們通常的經驗值是50倍。

我們來進一步看下 JIT 的行為:

  1. $ java -XX:+PrintFlagsFinal -version | grep CompileThreshold     intx Tier3CompileThreshold                     = 2000                                {product}     intx Tier4CompileThreshold                     = 15000                               {product} 

這里是兩項 JDK 內部的 JIT 參數的數值,我們暫不對分層編譯原理做過多介紹,可以參考Stack Overflow。Tier3 可以簡單理解為(client compiler)C1,Tier4 是 C2。當一個方法解釋執行2000次會進行 C1 編譯,當 C1 編譯后執行15000次后就會 C2 編譯,真正達到文章開頭的 C 的一半性能完全體。

在應用剛啟動階段,方法還沒有完全被JIT編譯完成,因此大部分情況停留在解釋執行,影響了應用啟動的速度。

三 如何優化 Java 應用的啟動速度

前面我們花了大量的篇幅分析了 Java 應用啟動慢的主要原因,總結下就是:

受到 JakartaEE 影響,常見框架考慮復用和靈活性,設計得比較復雜;
為了跨平臺性,代碼是動態加載,并且動態編譯的,啟動階段加載和執行耗時;
這兩者綜合起來造成了 Java 應用啟動慢的現狀。

Python 和 Javascript 都是動態解析加載模塊的,CPyhton 甚至沒有 JIT,理論上啟動不會比 Java 快很多,但是它們并沒有使用很復雜的應用框架,因此整體不會感受到啟動性能的問題。

雖然我們無法輕易去改變用戶對框架的使用習慣,但是可以在運行時層面進行增強,使啟動性能盡量靠近 Native image。OpenJDK 官方社區也一直在努力解決啟動性能問題,那么我們作為普通 Java 開發者,是否可以借助OpenJDK的最新特性來協助我們提升啟動性能呢?

Class Loading通過 JarIndex 解決 JAR 包遍歷問題,不過該技術過于古老,很難在現代的囊括了tomcat、fatJar的項目里使用起來AppCDS 可以解決 class 文件解析處理的性能問題
Class Initialize: OpenJDK9 加入了 HeapArchive,可以持久化一部分類初始化相關的 Heap 數據,不過只有寥寥數個 JDK 內部 class (比如 IntegerCache )可以被加速,沒有開放的使用方式。
JIT預熱: JEP295 實現了 AOT 編譯,但是存在 bug,使用不當會引發程序正確性能問題。在性能上沒有得到很好的 tuning,大部分情況下看不到效果,甚至會出現性能回退。
面對 OpenJDK 上述特性所存在的問題,Alibaba Dragonwell 對以上各項技術進行了研發優化,并與云產品進行了整合,用戶不需要投入太多精力就可以輕松地優化啟動時間。

1 AppCDS

CDS(Class Data Sharing)在Oracle JDK1.5被首次引入,在Oracle JDK8u40中引入了AppCDS,支持JDK以外的類 ,但是作為商業特性提供。隨后Oracle將AppCDS貢獻給了社區,在JDK10中CDS逐漸完善,也支持了用戶自定義類加載器(又稱AppCDS v2)。

面向對象語言將對象(數據)和方法(對象上的操作)綁定到了一起,來提供更強的封裝性和多態。這些特性都依賴對象頭中的類型信息來實現,Java、Python語言都是如此。Java對象在內存中的layout如下:

  1. +-------------+|  mark       |+-------------+|  Klass*     |+-------------+|  fields     ||             |+-------------+ 

mark 表示了對象的狀態,包括是否被加鎖、GC年齡等等。而Klass*指向了描述對象類型的數據結構 InstanceKlass :

  1. //  InstanceKlass layout://    [C++ vtbl pointer           ] Klass//    [java mirror                ] Klass//    [super                      ] Klass//    [access_flags               ] Klass//    [name                       ] Klass//    [methods                    ]//    [fields                     ]... 

基于這個結構,諸如 o instanceof String 這樣的表達式就可以有足夠的信息判斷了。要注意的是InstanceKlass結構比較復雜,包含了類的所有方法、field等等,方法又包含了字節碼等信息。這個數據結構是通過運行時解析class文件獲得的,為了保證安全性,解析class時還需要校驗字節碼的合法性( 非通過 Javac 產生的方法字節碼很容易引起 JVM crash)。

CDS 可以將這個解析、校驗產生的數據結構存儲(dump)到文件,在下一次運行時重復使用。這個dump產物叫做Shared Archive,以jsa后綴(Java shared archive)。

為了減少 CDS 讀取 jsa dump 的開銷,避免將數據反序列化到InstanceKlass的開銷,jsa 文件中的存儲layout和InstanceKlass對象完全一樣,這樣在使用 jsa 數據時,只需要將 jsa 文件映射到內存,并且讓對象頭中的類型指針指向這塊內存地址即可,十分高效。

  1. Object:+-------------+|  mark       |         +-------------------------++-------------+         |classes.jsa file         ||  Klass*     +--------->java_mirror|super|methods|+-------------+         |java_mirror|super|methods||  fields     |         |java_mirror|super|methods||             |         +-------------------------++-------------+ 

AppCDS 對 customer class loader 力不從心

jsa 中存儲的InstanceKlass是對class文件解析的產物。對于 boot classloader (就是加載jre/lib/rt.jar下面的類的classloader)和 system(app) classloader (加載-classpath下面的類的 classloader ),CDS有內部機制可以跳過對 class文件 的讀取,僅僅通過類名在 jsa 文件中匹配對應的數據結構。

Java 還提供用戶自定義類加載器(custom class loader)的機制,用戶通過Override自己的 Classloader.loadClass() 方法可以高度定制化獲取類的邏輯,比如從網絡上獲取、直接在代碼中動態生成都是可行的。為了增強AppCDS的安全性,避免因為從CDS加載了類定義反而獲得了非預期的類,AppCDS customer class loader需要經過如下步驟:

調用用戶定義的Classloader.loadClass(),拿到class byte stream
計算class byte stream的checksum,與jsa中的同類名結構的checksum比較
如果匹配成功則返回jsa中的InstanceKlass,否則繼續使用slow path解析class文件
我們看到許多場景下,上述的第一步占據了類加載耗時的大頭,此時 AppCDS 就顯得力不從心了。舉例來說:

  1. bar.jar +- com/bar/Bar.class baz.jar +- com/baz/Baz.class foo.jar +- com/foo/Foo.class 

class path 包含如上的三個jar包,在加載class com.foo.Foo 時,大部分Classloader實現(包括URLClassloader、tomcat、spring-boot)都選擇了最簡單的策略(過早的優化是萬惡之源): 按照jar包出現在磁盤的順序逐個嘗試抽取 com/foo/Foo.class 這個文件。

JAR 包使用了 zip 格式作為存儲,每次類加載都需要遍歷classpath下的 JAR 包們,嘗試從 zip 中抽取單個文件,來確保存在的類可以被找到。假設有N個 JAR 包,那么平均一個類加載需要嘗試訪問N/2個zip文件。

在我們的一個真實場景下,N到達2000,此時 JAR 包查找開銷非常大,并且遠大于InstanceKlass解析的開銷。面對此類場景 AppCDS 技術就力不從心了。

JAR Index

根據jar文件規范,JAR 文件是一種使用 zip封裝,并使用文本在META-INF目錄存儲元信息的格式。該格式在設計時已經考慮了應對上述的查找場景,這項技術叫做JAR Index。

假設我們要在上述的bar.jar、baz.jar、foo.jar中查找一個class,如果能夠通過類型com.foo.Foo,立刻推斷出具體在哪個jar包,就可以避免上述的掃描開銷了。

JarIndex-Version: 1.0foo.jarcom/foobar.jarcom/barbaz.jarcom/baz
通過 JAR Index 技術,可以生成出上述的索引文件INDEX.LIST。加載到內存后成為一個HashMap:

com/bar --> bar.jarcom/baz --> baz.jarcom/foo --> foo.jar
當我們看到類名com.foo.Foo,可以根據包名 com.foo 從索引中得知具體的jar包foo.jar,迅速抽取class文件。

Jar Index 技術看似解決了我們的問題,但是這項技術十分古老,很難在現代應用中被使用起來:

jar i 根據 META-INF/MANIFEST.MF 中的 Class-Path 屬性產生索引文件,現代項目幾乎不維護這個屬性
只有 URLClassloader 支持JAR Index
要求帶索引的jar盡量出現在 classpath 的前面
Dragonwell 通過 agent 注入使得 INDEX.LIST 能夠被正確地生成,并出現在 classpath 的合適位置來幫助應用提升啟動性能。

2 類提前初始化

類的 static block 中的代碼執行我們稱之為類初始化,類加載完成后必須執行完初始化代碼才能被使用(創建instance、調用 static 方法)。

很多類的初始化本質上只是構造一些static field:

  1. class IntegerCache {    static final Integer cache[];    static {        Integer[] c = new Integer[size];        int j = low;        for(int k = 0; k < c.length; k++)            c[k] = new Integer(j++);        cache = c;    }} 

我們知道 JDK 對 box type 中常用的一段區間有緩存,避免過多的重復創建,這段數據就需要提前構造好。由于這些方法只會被執行一次,因此是以純解釋的方式執行的,如果可以持久化幾個static字段的方式來避免調用類初始化器,我們就可以拿到提前初始化好的類,減少啟動時間。

將持久化加載到內存使用最高效的方式是內存映射:

  1. int fd = open("archive_file", O_READ);struct person *persons = mmap(NULL, 100 * sizeof(struct person),                              PROT_READ, fd, 0);int age = persons[5].age; 

C語言幾乎是直接面向內存來操作數據的,而Java這樣的高級語言都將內存抽象成了對象,有mark、Klass*等元信息,每次運行之間都存在一定的變化,因此需要更加復雜的機智來獲得高效的對象持久化。

Heap Archive簡介

OpenJDK9 引入了HeapArchive能力,OpenJDK12中heap archive 被正式使用。顧名思義,Heap Archive技術可以將堆上的對象持久化存儲下來。

對象圖被提前被構建好后放進archive,我們將這個階段稱為dump;而使用archive里的數據稱為運行時。dump和運行時通常不是一個進程,但在某些場景下也可以是同一個進程。

回憶下使用AppCDS后的內存布局,對象的Klass*指針指向了SharedArchive中的的數據。AppCDS對InstanceKlass這個元信息進行了持久化,如果想要復用持久化的對象,那么對象頭的類型指針必須也要指向一塊被持久化過的元信息,因此HeapArchive技術是依賴AppCDS的。

為了適應多種場景,OpenJDK的HeapArchive還提供了Open和Closed兩種級別:

上圖是允許的引用關系:

Closed Archive不允許引用Open Archive 和Heap中的對象可以引用Closed Archive內部的對象只讀,不可寫
Open Archive可以引用任何對象可寫
這樣設計的原因是對于一些只讀結構,放在Closed Archive 中可以做到對GC完全無開銷。

為什么只讀?想象一下,假如Closed Archive中的對象A引用了heap中的對象B,那么當對象B移動時,GC需要修正A中指向B的field,這會帶來GC開銷。

利用 Heap Archive 提前做類初始化

支持這種結構后,在類加載后,將static變量指向被Archive的對象,即可完成類初始化:

  1. class Foo {  static Object data;}                 +                  |        <---------+Open Archive Object:+-------------+|  mark       |         +-------------------------++-------------+         |classes.jsa file         ||  Klass*     +--------->java_mirror|super|methods|+-------------+         |java_mirror|super|methods||  fields     |         |java_mirror|super|methods||             |         +-------------------------++-------------+ 

3 AOT編譯

除去類的加載,方法的前幾次執行因為沒有被JIT編譯器給編譯,字節碼在解釋模式下執行。根據本文上半部分的分析,解釋執行速度約為JIT編譯后的幾十分之一,代碼解釋執行慢也啟動慢的一大元兇。

傳統的C/C++等語言都是直接編譯到目標平臺的native機器碼。隨著大家意識到Java、JS等解釋器JIT語言的啟動預熱問題,通過AOT將字節碼直接編譯到native代碼這種方式逐漸進入公眾視野。

wasm、GraalVM、OpenJDK都不同程度地支持了AOT編譯,我們主要圍繞JEP295引入的jaotc工具優化啟動速度。

注意這里的術語使用:
JEP295使用AOT是將class文件中的方法逐個編譯到native代碼片段,通過Java虛擬機在加載某個類后替換方法的的入口到AOT代碼。
而GraalVM的的Native Image功能是更加徹底的靜態編譯,通過一個用Java代碼編寫的小型運行時SubstrateVM,該運行時和應用代碼一起被靜態編譯到可執行的文件(類似Go),不再依賴JVM。該做法也是一種AOT,但是為了區分術語,這里的AOT單指JEP295的方式。

AOT特性初體驗

通過JEP295的介紹,我們可以快速體驗AOT

jaotc 命令會調用Graal編譯器對字節碼進行編譯,產生 libHelloWorld.so 文件。這里產生的so文件容易讓人誤以為會直接像JNI一樣調用進編譯好的庫代碼。但是這里并沒有完全使用ld的加載機制來運行代碼,so文件更像是當做一個 native 代碼的容器。hotsopt runtime 在加載 AOT so 后需要進行進一步的動態鏈接。在類加載后hotspot 會自動關聯 AOT 代碼入口,對于下次方法調用使用 AOT 版本。而 AOT 生成的代碼也會主動與 hotspot 運行時交互,在aot、解釋器、JIT 代碼間相互跳轉。

1)AOT 的一波三折

看起來JEP295已經實現了一套完備的AOT體系,但是為何不見這項技術被大規模使用?在 OpenJDK 的各項新特性中,AOT 算得上是命途多舛。

2)多 Classloader 問題

JDK-8206963: bug with multiple class loaders

這是在設計上沒有考慮到Java的多 Classloader 場景,當多個 Classloader 加載的同名類都使用了 AOT 后,他們的 static field 是共享的,而根據 Java 語言的設計,這部分數據應該是隔開的。

由于沒有可以快速修復這個問題的方案,OpenJDK 僅僅是添加了如下代碼:

  1. ClassLoaderData* cld = ik->class_loader_data();  if (!cld->is_builtin_class_loader_data()) {    log_trace(aot, class, load)("skip class  %s  for custom classloader %s (%p) tid=" INTPTR_FORMAT,                                ik->internal_name(), cld->loader_name(), cld, p2i(thread));    return false;} 

對于用戶自定義類加載器不允許使用 AOT。從這里已經可以初步看出該特性在社區層面已經逐漸缺乏維護。

在這種情況下,雖然通過 class-path 指定的類依然可以使用 AOT,但是我們常用的 spring-boot、Tomcat 等框架都需要通過 Custom Classloader 加載應用代碼。可以說這一改變切掉了 AOT 的一大塊場景。

3)缺乏調優和維護,退回成實驗特性

JDK-8227439: Turn off AOT by default

JEP 295 AOT is still experimental, and while it can be useful for startup/warmup when used with custom generated archives tailored for the application, experimental data suggests that generating shared libraries at a module level has overall negative impact to startup, dubious efficacy for warmup and severe static footprint implications.

從此打開 AOT 需要添加 experimental 參數:

java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=...
根據 issue 的描述,這項特性編譯整個模塊的情況下,對啟動速度和內存占用都起到了反作用。我們分析的原因如下:

Java 語言本身過分復雜,動態類加載等運行時機制導致 AOT 代碼沒法運行得像預期一樣快
AOT 技術作為階段性的項目在進入 Java 9 之后并沒有被長期維護,缺乏必要的調優(反觀AppCDS一直在迭代優化)

4)JDK16 中被刪除

JDK-8255616:Disable AOT and Graal in Oracle OpenJDK

在 OpenJDK16 發布前夕,Oracle正式決定不再維護這項技術:

We haven't seen much use of these features, and the effort required to support and enhance them is significant.

其根本原因還是這項基于缺乏必要的優化和維護。而對于 AOT 相關的未來的規劃,只能從只言片語中推測將來Java的AOT 有兩種技術方向:

在 OpenJDK 的 C2 基礎上做 AOT

在 GraalVM 的 native-image 上支持完整的 Java 語言特性,需要 AOT 的用戶逐漸從 OpenJDK 過渡到native-image
上述的兩個技術方向都沒法在短期內看到進展,因此 Dragonwell 的技術方向是讓現有的 JEP295 更好地工作,為用戶帶來極致的啟動性能。

5)Dragonwell 上的快速啟動

Dragonwell 的快速啟動特性攻關了 AppCDS、AOT 編譯技術上的弱點,并基于 HeapArchive 機制研發了類提前初始化特性。這些特性將 JVM 可見的應用啟動耗時幾乎全部消除。

此外,因為上述幾項技術都符合 trace-dump-replay 的使用模式,Dragonwell 將上述啟動加速技術統一了流程,并且集成到了 SAE 產品中。

四 SAE x Dragonwell : Serverless with Java 啟動加速最佳實踐

有了好的食材,還需要相匹配的佐料,以及一位烹飪大師。

將 Dragonwell 的啟動加速技術和和以彈性著稱的 Serverless 技術相結合更相得益彰,同時共同落地在微服務應用的全生命周期管理中,才能發揮他們縮短應用端到端啟動時間的作用,因此 Dragonwell 選擇了 SAE 來落地其啟動加速技術。

SAE (Serverless 應用引擎)是首款面向 Serverless 的 PaaS 平臺,他可以:

Java 軟件包部署:零代碼改造享受微服務能力,降低研發成本
Serverless 極致彈性:資源免運維,快速擴容應用實例, 降低運維與學習成本

1 難點分析

通過分析,我們發現微服務的用戶在應用啟動層面面臨著一些難題:

軟件包大:幾百 MB 甚至 GB 級別
依賴包多:上百個依賴包,幾千個 Class
加載耗時:從磁盤加載依賴包,再到 Class 按需加載,最高可占啟動耗時的一半
借助 Dragonwell 快速啟動能力,SAE 為 Serverless Java 應用提供了一套,讓應用盡可能加速啟動的最佳實踐,讓開發者更專注于業務開發:

Java 環境 + JAR/WAR 軟件包部署:集成 Dragonwell 11 ,提供加速啟動環境
JVM 快捷設置:支持一鍵開啟快速啟動,簡化操作
NAS 網盤:支持跨實例加速,在新包部署時,加速新啟動實例/分批發布啟動速度

2 加速效果

我們選擇一些微服務、復雜依賴的業務場景典型 Demo 或內部應用,測試啟動效果,發現應用普遍能降低 5%~45% 的啟動耗時。若應用啟動,存在下列場景,會有明顯加速效果:

類加載多(spring-petclinic 啟動加載約 12000+ classes)

依賴外部數據越少

3 客戶案例

阿里巴巴搜索推薦 Serverless 平臺

阿里內部的搜索推薦 Serverless 平臺通過類加載隔離機制,將多個業務的合并部署在同一個 Java 虛擬機中。調度系統會按需地將業務代碼合并部署到空閑的容器中,讓多個業務可以共享同一個資源池,大大提高部署密度和整體的 CPU 使用率。

由于要支撐大量不同的業務研發運行,平臺本身需要提供足夠豐富的功能,如緩存、RPC調用。因此搜索推薦Serverless 平臺的每個 JVM 都需要拉起類似 Pandora Boot 的中間件隔離容器,這將加載大量的類,拖累了平臺自身的啟動速度。當突增的需求進入,調度系統需要拉起更多容器以供業務代碼部署,此時容器本身的啟動時間就顯得尤為重要。

基于 Dragonwell 的快速啟動技術,搜索推薦平臺在預發布環境會執行 AppCDS、Jarindex 等優化,將產生的 archive 文件打入容器鏡像中,這樣每一個容器在啟動時都能享受加速,減少約30%的啟動耗時。

潮牌秒殺SAE極致彈性

某外部客戶,借助 SAE 提供的 Jar 包部署與 Dragonwell 11,快速迭代上線了某潮牌商場 App。

在面對大促秒殺時,借助 SAE Serverless 極致彈性,與應用指標 QPS RT 指標彈性能力,輕松面對 10 倍以上快速擴容需求;同時一鍵開啟 Dragonwell 增強的 AppCDS 啟動加速能力,降低 Java 應用 20% 以上啟動耗時,進一步加速應用啟動,保證業務平穩健康運行。

五 總結

Dragonwell 上的快速啟動技術方向上完全基于 OpenJDK 社區的工作,對各項功能進行了細致的優化與 bugfix,并降低了上手的難度。這樣做既保證了對標準的兼容,避免內部定制,也能夠為開源社區做出貢獻。

作為基礎軟件,Dragonwell 只能生成/使用磁盤上的 archive 文件。結合 SAE 對 Dragonwell 的無縫集成,JVM 配置、archive 文件的分發都被自動化。客戶可以輕松享受應用加速帶來的技術紅利。

責任編輯:梁菲 來源: 阿里云云棲號
相關推薦

2024-12-03 11:12:47

2009-07-10 09:03:27

Myeclise 7.優化

2009-09-04 11:34:31

NetBeans優化

2021-09-03 09:44:13

移動端性能優化U-APM

2018-09-29 15:59:18

APPiOS優化

2013-07-01 10:07:44

JavaSpringHibernate

2023-12-06 08:30:02

Spring項目

2013-07-03 09:52:13

熱部署熱替換

2014-04-02 17:10:00

虛擬應用工作原理

2021-10-31 19:51:08

USB傳輸速度

2009-10-29 14:07:35

ROF接入技術

2023-06-06 08:20:07

運維Spring內存

2021-07-12 23:43:46

AppAndroid優化

2010-11-26 14:33:10

MySQL系統變量

2022-09-01 09:35:13

java應用工具

2011-06-29 14:27:58

網站優化

2019-03-15 15:00:49

Webpack構建速度前端

2009-10-19 14:46:59

綜合布線系統

2009-10-28 10:26:36

2010-03-01 09:01:01

Windows 7啟動速度
點贊
收藏

51CTO技術棧公眾號

国产日产欧美a一级在线| 日韩av在线导航| 一级一片免费播放| 天天久久综合网| 99热国产在线| 91丨国产丨九色丨pron| 国产精品久久国产精品99gif| 日日噜噜夜夜狠狠久久波多野| 国产日韩一区二区三免费高清| 午夜欧美在线一二页| 欧美一级日本a级v片| 精品国产va久久久久久久| 久久九九99| 欧美激情18p| 中文字幕黄色网址| 久久影院资源站| 欧美精品三级日韩久久| 成人毛片视频网站| 国产高潮流白浆喷水视频| 国产成人黄色| 欧美不卡一二三| 免费观看黄色的网站| 五月天福利视频| 国产一区二区成人久久免费影院| 日韩av片永久免费网站| 69av视频在线| 日韩综合网站| 欧美日韩激情一区| 成年人视频观看| 亚洲小说区图片区都市| 国产精品乱码一区二区三区软件| 国产视频一区二区三区四区| 国产精品伦一区二区三区| 手机在线电影一区| 亚洲欧美国产视频| 漂亮人妻被黑人久久精品| 四虎影视国产精品| 欧美三区在线视频| 国产成人手机视频| 视频二区不卡| 欧美性开放视频| 国产精品久久..4399| 日韩影视在线| 亚洲免费在线视频| 国产又粗又大又爽的视频| 亚乱亚乱亚洲乱妇| 久久久久久久电影| 欧美人与性禽动交精品| 波多野结衣家庭主妇| 一区二区三区国产在线| 国模叶桐国产精品一区| 久久av无码精品人妻系列试探| av毛片精品| 日韩一级片在线观看| 伦伦影院午夜理论片| 99tv成人影院| 日韩欧美国产一区二区在线播放| 国产不卡一区二区视频| 七七成人影院| 99精品国产视频| 国产精品久久久久久久久久东京| 国产精品久免费的黄网站| 亚洲永久视频| 人人澡人人澡人人看欧美| 日本高清不卡码| 久久精品一区二区国产| 国产精品亚洲美女av网站| 中文字幕一区二区久久人妻| 另类综合日韩欧美亚洲| 91久久精品国产| 午夜精品久久久久久久96蜜桃| 国产成人精品影视| 精品在线视频一区二区三区| 青青草超碰在线| 中文字幕高清不卡| 国产91av视频在线观看| 在线中文字幕电影| 五月婷婷激情综合| 免费黄色一级网站| 91精品视频一区二区| 欧美成人官网二区| 国产美女喷水视频| 欧美gayvideo| 欧美黄色性视频| 日韩欧美国产另类| 国产一区在线观看麻豆| 国产乱码精品一区二区三区卡 | 亚洲黄色www| 一道本在线免费视频| 国产精品视频首页| 亚洲精品美女在线| 国产人与禽zoz0性伦| 欧美黄色aaaa| 国产精品678| av 一区二区三区| 91免费国产视频网站| 亚洲开发第一视频在线播放| 四虎影视在线播放| 国产欧美一区二区精品性色| 精品999在线观看| 黄色片在线看| 夜夜操天天操亚洲| 亚洲精品怡红院| 国产毛片精品| 俺也去精品视频在线观看| 国产精品suv一区二区69| 奇米在线7777在线精品| 国产精品一区在线播放| 99青草视频在线播放视| 久久久精品免费免费| av磁力番号网| 成人国产一区| 精品国产乱码久久久久久闺蜜 | 欧美在线激情网| 一级片视频网站| ww久久中文字幕| 青青草视频在线视频| 国模私拍一区二区国模曼安| 亚洲最大色网站| 在线视频日韩一区 | 日韩久久不卡| 成年人在线网站| 日韩一区二区三区观看| 懂色av粉嫩av浪潮av| 国产一区二区精品| 福利视频一区二区三区| 九义人在线观看完整免费版电视剧| 国产三级精品三级| 我的公把我弄高潮了视频| av日韩久久| 色青青草原桃花久久综合| 日日噜噜噜噜人人爽亚洲精品| 国产成人av一区二区三区在线| 污视频在线免费观看一区二区三区 | 擼擼色在线看观看免费| 日韩美一区二区三区| 91狠狠综合久久久| 91精品啪在线观看国产18| 国产99在线|中文| 飘雪影视在线观看免费观看 | 国产又粗又大又爽视频| 国产欧美日韩视频在线观看| 黄色动漫在线免费看| 欧美变态网站| 亚洲3p在线观看| 欧美不卡视频在线观看| 国产成人精品一区二区三区四区| 亚洲小视频在线播放| 国产精品成人3p一区二区三区| 色婷婷av一区二区三区在线观看 | 白浆在线视频| 亚洲国产黄色片| 四虎永久在线精品| av成人免费在线| 国产成人黄色片| 久草成人在线| 国产成人精品久久| 91caoporn在线| 666欧美在线视频| 午夜免费激情视频| 成人精品视频网站| 任我爽在线视频精品一| 网友自拍亚洲| 中文字幕亚洲一区在线观看| 国产精品自偷自拍| 一区二区三区在线免费| 91丨porny丨对白| 美女久久一区| 亚洲一二区在线| 一区二区日韩| 69久久夜色精品国产69乱青草| 日av在线播放| 欧美男人的天堂一二区| 免费网站看av| 久久网站最新地址| 午夜久久久精品| 欧美特黄视频| 欧美日韩国产综合视频在线| 69堂精品视频在线播放| 久久国产精品久久久久| 人妻偷人精品一区二区三区| 日韩欧美国产一区二区| 一级性生活免费视频| 国产99久久久久| 91视频免费版污| 你懂的国产精品| 免费精品视频一区| 国产精久久一区二区| 欧美一区二区三区艳史| 日本在线观看网站| 亚洲精品国精品久久99热| 欧美视频xxxx| 亚洲大片在线观看| 国产福利在线导航| 成人av第一页| 亚洲视频一二三四| 一区二区毛片| 国产一二三四区在线观看| 天堂av一区二区三区在线播放| 国产欧美一区二区白浆黑人| 成人ssswww在线播放| 最近2019年好看中文字幕视频 | 久久久精品黄色| 无套白嫩进入乌克兰美女| 性一交一乱一区二区洋洋av| youjizz.com亚洲| 香蕉人人精品| av免费观看久久| 日本成人一区二区| 欧美一级淫片丝袜脚交| 四虎影院观看视频在线观看| 国产一区二区三区视频免费| 欧美另类一区二区| 综合精品久久久| 99999精品| 久久这里有精品15一区二区三区| 免费的av在线| 久久影院一区| 欧美一区二区在线| 久久悠悠精品综合网| 99精彩视频在线观看免费| 久久xxx视频| 日韩av色在线| 亚洲欧洲日本韩国| 97精品视频在线播放| 伊人精品影院| 免费91麻豆精品国产自产在线观看| 东凛在线观看| 亚洲桃花岛网站| 日本在线一二三| 日韩精品在线第一页| 欧美一区二区三区成人片在线| 91精品国产黑色紧身裤美女| 中文字幕人妻互换av久久 | 久久久久久久成人| 国产一区久久精品| 色婷婷久久一区二区| 国产理论电影在线观看| 亚洲另类图片色| 青青草av免费在线观看| 亚洲人成在线观| 精品电影在线| 国产午夜精品全部视频播放| 毛片在线播放网站| 国产一区二区三区视频在线观看| 国产粉嫩一区二区三区在线观看| 亚洲欧美日韩图片| 国产精品影院在线| 尤物精品国产第一福利三区| 第九色区av在线| 中文字幕日韩高清| 激情在线小视频| 欧美裸体xxxx极品少妇| 高h震动喷水双性1v1| 日韩欧美黄色影院| 亚洲经典一区二区| 亚洲国产精品yw在线观看| 少妇荡乳情欲办公室456视频| 亚洲国产精品电影| 青草久久伊人| 日韩亚洲欧美中文在线| 韩国av网站在线| 欧美激情aaaa| 免费h视频在线观看| 日韩av大片免费看| 欧美成人三级| 91传媒在线免费观看| 国产劲爆久久| 欧洲精品一区色| 国产精品久久天天影视| 一本大道东京热无码aⅴ| 欧美一区在线看| 免费看国产曰批40分钟| 国产高清久久| 毛片在线视频观看| 国产精品久久久亚洲一区| 美女喷白浆视频| 国模大尺度一区二区三区| 性活交片大全免费看| 久久久久久久一区| 久久久久久视频| 欧美日韩国产在线| 一卡二卡三卡在线| 亚洲第一区在线观看| 激情小视频在线| 欧美成人黄色小视频| 亚洲美女尤物影院| 国产在线观看精品| 精品欧美午夜寂寞影院| 亚洲精品乱码视频| 极品日韩av| 手机在线成人免费视频| 成人一道本在线| 麻豆视频免费在线播放| 五月婷婷久久丁香| 国产男女猛烈无遮挡| 日韩成人在线视频观看| 国产乱色在线观看| 欧美在线视频网站| 999久久精品| 伊人久久大香线蕉成人综合网 | 国产精品色在线观看| 麻豆成人在线视频| 欧美日韩另类一区| 亚洲欧美综合一区二区| 欧美大成色www永久网站婷| 芒果视频成人app| 97超碰国产精品女人人人爽| 欧美美女福利视频| 欧美精品免费观看二区| 国产综合视频| 韩国一区二区在线播放| 国产亚洲女人久久久久毛片| 日本特黄一级片| 正在播放一区二区| 婷婷激情在线| 国产成人精品一区| 日韩影视在线观看| 福利视频一区二区三区四区| 精品一区二区精品| 日本综合在线观看| 色综合久久久久综合| 偷拍精品一区二区三区| 欧美精品videos| 亚洲91网站| 粉嫩av一区二区三区天美传媒 | 亚洲激情另类| 丰满少妇一区二区三区专区| 中文字幕一区二区视频| 无码人妻精品一区二区三区蜜桃91| 亚洲福利视频专区| 日本资源在线| 粉嫩高清一区二区三区精品视频| 亚洲男女av一区二区| 少妇一级淫免费播放| 欧美国产精品劲爆| 无码人妻一区二区三区线| 亚洲人成77777在线观看网| 中老年在线免费视频| 久久国产精品久久| 中文亚洲免费| www.久久国产| 日韩欧美亚洲成人| 免费黄色片在线观看| 国产成人精品优优av| 欧美日韩在线观看视频小说| 在线观看精品视频| 久久精品久久精品| 欧美性生交大片| 欧美一区二区三区日韩视频| а√天堂8资源在线官网| 亚洲最大av网| 在线观看一区视频| 日本黄色动态图| 色综合久久久久久久久久久| 高清毛片在线看| 91久久久久久| 好吊视频一区二区三区四区| 国产精品成人99一区无码 | 亚洲精品一区二区二区| 一区二区三区视频免费| 小说区图片区亚洲| 亚洲中文字幕无码一区二区三区| 懂色av中文字幕一区二区三区| 久久久久久久久久91| 亚洲精品国精品久久99热 | 欧美熟乱第一页| 黄色小网站在线观看| 波多野结衣成人在线| 一本久久综合| 2019男人天堂| 日韩一级片在线观看| 涩涩在线视频| 伊人天天久久大香线蕉av色| 国产成人精品免费在线| 午夜毛片在线观看| 色视频www在线播放国产成人| 一区二区亚洲视频| 黄色片一级视频| 亚洲乱码国产乱码精品精的特点 | 日韩av黄色在线| 国产精品视频分类| 亚洲一区二区三区爽爽爽爽爽 | 日本在线观看a| 亚洲欧美另类小说| 老熟妇高潮一区二区高清视频| 日本久久亚洲电影| 国产精品毛片一区二区在线看| 国内精品免费视频| 欧美情侣在线播放| 超碰在线资源| 无遮挡亚洲一区| 成人久久18免费网站麻豆| 五月激情丁香网| 久久久久久久久久婷婷| 秋霞欧美视频| 在线观看国产免费视频| 制服.丝袜.亚洲.另类.中文| 亚洲美女久久精品| 国产传媒久久久|