最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot優(yōu)化后啟動速度快到飛起技巧示例

 更新時(shí)間:2022年07月23日 09:22:03   作者:艾小仙  
這篇文章主要為大家介紹了Spring Boot優(yōu)化后啟動速度快到飛起的技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

微服務(wù)用到一時(shí)爽,沒用好就呵呵啦,特別是對于服務(wù)拆分沒有把控好業(yè)務(wù)邊界、拆分粒度過大等問題,某些 Spring Boot 啟動速度太慢了,可能你也會有這種體驗(yàn),這里將探索一下關(guān)于 Spring Boot 啟動速度優(yōu)化的一些方方面面。

啟動時(shí)間分析

IDEA 自帶集成了 async-profile 工具,所以我們可以通過火焰圖來更直觀的看到一些啟動過程中的問題,比如下圖例子當(dāng)中,通過火焰圖來看大量的耗時(shí)在 Bean 加載和初始化當(dāng)中。

圖來自 IDEA 自帶集成的 async-profile 工具,可在 Preferences 中搜索 Java Profiler 自定義配置,啟動使用 Run with xx Profiler。

y 軸表示調(diào)用棧,每一層都是一個(gè)函數(shù),調(diào)用棧越深,火焰就越高,頂部就是正在執(zhí)行的函數(shù),下方都是它的父函數(shù)。

x 軸表示抽樣數(shù),如果一個(gè)函數(shù)在 x 軸占據(jù)的寬度越寬,就表示它被抽到的次數(shù)多,即執(zhí)行的時(shí)間長。

啟動優(yōu)化

減少業(yè)務(wù)初始化

大部分的耗時(shí)應(yīng)該都在業(yè)務(wù)太大或者包含大量的初始化邏輯,比如建立數(shù)據(jù)庫連接、Redis連接、各種連接池等等,對于業(yè)務(wù)方的建議則是盡量減少不必要的依賴,能異步則異步。

延遲初始化

Spring Boot 2.2版本后引入 spring.main.lazy-initialization屬性,配置為 true 表示所有 Bean 都將延遲初始化。

可以一定程度上提高啟動速度,但是第一次訪問可能較慢。

spring.main.lazy-initialization=true

Spring Context Indexer

Spring5 之后版本提供了spring-context-indexer功能,主要作用是解決在類掃描的時(shí)候避免類過多導(dǎo)致的掃描速度過慢的問題。

使用方法也很簡單,導(dǎo)入依賴,然后在啟動類打上@Indexed注解,這樣在程序編譯打包之后會生成META-INT/spring.components文件,當(dāng)執(zhí)行ComponentScan掃描類時(shí),會讀取索引文件,提高掃描速度。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-indexer</artifactId>
  <optional>true</optional>
</dependency>

關(guān)閉JMX

Spring Boot 2.2.X 版本以下默認(rèn)會開啟 JMX,可以使用 jconsole 查看,對于我們無需這些監(jiān)控的話可以手動關(guān)閉它。

spring.jmx.enabled=false

關(guān)閉分層編譯

Java8 之后的版本,默認(rèn)打開多層編譯,使用命令java -XX:+PrintFlagsFinal -version | grep CompileThreshold查看。

Tier3 就是 C1、Tier4 就是 C2,表示一個(gè)方法解釋編譯 2000 次進(jìn)行 C1編譯,C1編譯后執(zhí)行 15000 次會進(jìn)行 C2編譯。

我們可以通過命令使用 C1 編譯器,這樣就不存在 C2 的優(yōu)化階段,能夠提高啟動速度,同時(shí)配合 -Xverify:none/ -noverify 關(guān)閉字節(jié)碼驗(yàn)證,但是,盡量不要在線上環(huán)境使用。

-XX:TieredStopAtLevel=1 -noverify

另外的思路

上面介紹了一些從業(yè)務(wù)層面、啟動參數(shù)之類的優(yōu)化,下面我們再看看基于 Java 應(yīng)用本身有哪些途徑可以進(jìn)行優(yōu)化。

在此之前,我們回憶一下 Java 創(chuàng)建對象的過程,首先要進(jìn)行類加載,然后去創(chuàng)建對象,對象創(chuàng)建之后就可以調(diào)用對象方法了,這樣就還會涉及到 JIT,JIT通過運(yùn)行時(shí)將字節(jié)碼編譯為本地機(jī)器碼來提高 Java 程序的性能。

因此,下面涉及到的技術(shù)將會概括以上涉及到的幾個(gè)步驟。

JAR Index

Jar包其實(shí)本質(zhì)上就是一個(gè) ZIP 文件,當(dāng)加載類的時(shí)候,我們通過類加載器去遍歷Jar包,找到對應(yīng)的 class 文件進(jìn)行加載,然后驗(yàn)證、準(zhǔn)備、解析、初始化、實(shí)例化對象。

JarIndex 其實(shí)是一個(gè)很古老的技術(shù),就是用來解決在加載類的時(shí)候遍歷 Jar 性能問題,早在 JDK1.3的版本中就已經(jīng)引入。

假設(shè)我們要在A\B\C 3個(gè)Jar包中查找一個(gè)class,如果能夠通過類型com.C,立刻推斷出具體在哪個(gè)jar包,就可以避免遍歷 jar 的過程。

A.jar
com/A
B.jar
com/B
C.jar
com/C

通過 Jar Index 技術(shù),就可以生成對應(yīng)的索引文件 INDEX.LIST。

com/A --> A.jar
com/B --> B.jar
com/C --> C.jar

不過對于現(xiàn)在的項(xiàng)目來說,Jar Index 很難應(yīng)用:

  • 通過 jar -i 生成的索引文件是基于 META-INF/MANIFEST.MF 中的 Class-Path 來的,我們目前大多項(xiàng)目都不會涉及到這個(gè),所以索引文件的生成需要我們自己去做額外處理
  • 只支持 URLClassloader,需要我們自己自定義類加載邏輯

APPCDS

App CDS 全稱為 Application Class Data Sharing,主要是用于啟動加速和節(jié)省內(nèi)存,其實(shí)早在在 JDK1.5 版本就已經(jīng)引入,只是在后續(xù)的版本迭代過程中在不斷的優(yōu)化升級,JDK13 版本中則是默認(rèn)打開,早期的 CDS 只支持BootClassLoader, 在 JDK8 中引入了 AppCDS,支持 AppClassLoader 和 自定義的 ClassLoader。

我們都知道類加載的過程中伴隨解析、校驗(yàn)這個(gè)過程,CDS 就是將這個(gè)過程產(chǎn)生的數(shù)據(jù)結(jié)構(gòu)存儲到歸檔文件中,在下次運(yùn)行的時(shí)候重復(fù)使用,這個(gè)歸檔文件被稱作 Shared Archive,以jsa作為文件后綴。

在使用時(shí),則是將 jsa 文件映射到內(nèi)存當(dāng)中,讓對象頭中的類型指針指向該內(nèi)存地址。

讓我們一起看看怎么使用。

首先,我們需要生成希望在應(yīng)用程序之間共享的類列表,也即是 lst文件。對于 Oracle JDK 需要加入 -XX:+UnlockCommercialFeature 命令來開啟商業(yè)化的能力,openJDK 無需此參數(shù),JDK13的版本中將1、2兩步合并為一步,但是低版本還是需要這樣做。

java -XX:DumpLoadedClassList=test.lst

然后得到 lst 類列表之后,dump 到適合內(nèi)存映射的 jsa 文件當(dāng)中進(jìn)行歸檔。

java -Xshare:dump -XX:SharedClassListFile=test.lst -XX:SharedArchiveFile=test.jsa

最后,在啟動時(shí)加入運(yùn)行參數(shù)指定歸檔文件即可。

-Xshare:on -XX:SharedArchiveFile=test.jsa

需要注意的是,AppCDS只會在包含所有 class 文件的 FatJar 生效,對于 SpringBoot 的嵌套 Jar 結(jié)構(gòu)無法生效,需要利用 maven shade plugin 來創(chuàng)建 shade jar。

<build>
  <finalName>helloworld</finalName>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-shade-plugin</artifactId>
      <configuration>
        <keepDependenciesWithProvidedScope>true</keepDependenciesWithProvidedScope>
        <createDependencyReducedPom>false</createDependencyReducedPom>
        <filters>
          <filter>
            <artifact>*:*</artifact>
            <excludes>
              <exclude>META-INF/*.SF</exclude>
              <exclude>META-INF/*.DSA</exclude>
              <exclude>META-INF/*.RSA</exclude>
            </excludes>
          </filter>
        </filters>
      </configuration>
      <executions>
        <execution>
          <phase>package</phase>
          <goals><goal>shade</goal></goals>
          <configuration>
            <transformers>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/spring.handlers</resource>
              </transformer>
              <transformer implementation="org.springframework.boot.maven.PropertiesMergingResourceTransformer">
                <resource>META-INF/spring.factories</resource>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                <resource>META-INF/spring.schemas</resource>
              </transformer>
              <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
              <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                <mainClass>${mainClass}</mainClass>
              </transformer>
            </transformers>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

然后按照上述的步驟使用才可以,但是如果項(xiàng)目過大,文件數(shù)大于65535啟動會報(bào)錯(cuò):

Caused by: java.lang.IllegalStateException: Zip64 archives are not supported

源碼如下:

public int getNumberOfRecords() {
  long numberOfRecords = Bytes.littleEndianValue(this.block, this.offset + 10, 2);
  if (numberOfRecords == 0xFFFF) {
    throw new IllegalStateException("Zip64 archives are not supported");
}

在 2.2 及以上版本修復(fù)了這個(gè)問題,所以使用的時(shí)候盡量使用高版本可以避免此類問題的出現(xiàn)。

Heap Archive

JDK9 中引入了HeapArchive,并且 JDK12 中被正式使用,我們可以認(rèn)為 Heap Archive 是對 APPCDS 的一個(gè)延伸。

APPCDS 是持久化了類加載過程中驗(yàn)證、解析產(chǎn)生的數(shù)據(jù),Heap Archive 則是類初始化(執(zhí)行 static 代碼塊 cinit 進(jìn)行初始化) 相關(guān)的堆內(nèi)存的數(shù)據(jù)。

簡單來講,可以認(rèn)為 HeapArchive 是在類初始化的時(shí)候通過內(nèi)存映射持久化了一些 static 字段,避免調(diào)用類初始化器,提前拿到初始化好的類,提高啟動速度。

AOT編譯

我們說過,JIT 是通過運(yùn)行時(shí)將字節(jié)碼編譯為本地機(jī)器碼,需要的時(shí)候直接執(zhí)行,減少了解釋的時(shí)間,從而提高程序運(yùn)行速度。

上面我們提到的 3 個(gè)提高應(yīng)用啟動速度的方式都可以歸為類加載的過程,到真正創(chuàng)建對象實(shí)例、執(zhí)行方法的時(shí)候,由于可能沒有被 JIT 編譯,在解釋模式下執(zhí)行的速度非常慢,所以產(chǎn)生了 AOT 編譯的方式。

AOT(Ahead-Of-Time) 指的是程序運(yùn)行之前發(fā)生的編譯行為,他的作用相當(dāng)于是預(yù)熱,提前編譯為機(jī)器碼,減少解釋時(shí)間。

比如現(xiàn)在 Spring Cloud Native 就是這樣,在運(yùn)行時(shí)直接靜態(tài)編譯成可執(zhí)行文件,不依賴 JVM,所以速度非???。

但是 Java 中 AOT 技術(shù)不夠成熟,作為實(shí)驗(yàn)性的技術(shù)在 JDK8 之后版本默認(rèn)關(guān)閉,需要手動打開。

java -XX:+UnlockExperimentalVMOptions -XX:AOTLibrary=

并且由于長期缺乏維護(hù)和調(diào)優(yōu)這項(xiàng)技術(shù),在 JDK 16 的版本中已經(jīng)被移除,這里就不再贅述了。

下線時(shí)間優(yōu)化

優(yōu)雅下線

Spring Boot 在 2.3 版本中增加了新特性優(yōu)雅停機(jī),支持Jetty、Reactor Netty、Tomcat 和 Undertow,使用方式:

server:
  shutdown: graceful
# 最大等待時(shí)間
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

如果低于 2.3 版本,官方也提供了低版本的實(shí)現(xiàn)方案,新版本中的實(shí)現(xiàn)基本也是這個(gè)邏輯,先暫停外部請求,關(guān)閉線程池處理剩余的任務(wù)。

@SpringBootApplication
@RestController
public class Gh4657Application {
    public static void main(String[] args) {
        SpringApplication.run(Gh4657Application.class, args);
    }
    @RequestMapping("/pause")
    public String pause() throws InterruptedException {
        Thread.sleep(10000);
        return "Pause complete";
    }
    @Bean
    public GracefulShutdown gracefulShutdown() {
        return new GracefulShutdown();
    }
    @Bean
    public EmbeddedServletContainerCustomizer tomcatCustomizer() {
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                if (container instanceof TomcatEmbeddedServletContainerFactory) {
                    ((TomcatEmbeddedServletContainerFactory) container)
                            .addConnectorCustomizers(gracefulShutdown());
                }
            }
        };
    }
    private static class GracefulShutdown implements TomcatConnectorCustomizer,
            ApplicationListener<ContextClosedEvent> {
        private static final Logger log = LoggerFactory.getLogger(GracefulShutdown.class);
        private volatile Connector connector;
        @Override
        public void customize(Connector connector) {
            this.connector = connector;
        }
        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            this.connector.pause();
            Executor executor = this.connector.getProtocolHandler().getExecutor();
            if (executor instanceof ThreadPoolExecutor) {
                try {
                    ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                    threadPoolExecutor.shutdown();
                    if (!threadPoolExecutor.awaitTermination(30, TimeUnit.SECONDS)) {
                        log.warn("Tomcat thread pool did not shut down gracefully within "
                                + "30 seconds. Proceeding with forceful shutdown");
                    }
                }
                catch (InterruptedException ex) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
}

Eureka服務(wù)下線時(shí)間

另外,對于客戶端感知服務(wù)端下線時(shí)間方面的問題,我在之前的文章有提及到。

Eureka 使用了三級緩存來保存服務(wù)的實(shí)例信息。

服務(wù)注冊的時(shí)候會和 server 保持一個(gè)心跳,這個(gè)心跳的時(shí)間是 30 秒,服務(wù)注冊之后,客戶端的實(shí)例信息保存到 Registry 服務(wù)注冊表當(dāng)中,注冊表中的信息會立刻同步到 readWriteCacheMap 之中。

而客戶端如果感知到這個(gè)服務(wù),要從 readOnlyCacheMap 去讀取,這個(gè)只讀緩存需要 30 秒的時(shí)間去從 readWriteCacheMap 中同步。

客戶端和 Ribbon 負(fù)載均衡 都保持一個(gè)本地緩存,都是 30 秒定時(shí)同步。

按照上面所說,我們來計(jì)算一下客戶端感知到一個(gè)服務(wù)下線極端的情況需要多久。

  • 客戶端每隔 30 秒會發(fā)送心跳到服務(wù)端
  • registry 保存了所有服務(wù)注冊的實(shí)例信息,他會和 readWriteCacheMap 保持一個(gè)實(shí)時(shí)的同步,而 readWriteCacheMap 和 readOnlyCacheMap 會每隔 30 秒同步一次。
  • 客戶端每隔 30 秒去同步一次 readOnlyCacheMap 的注冊實(shí)例信息
  • 考慮到如果使用 ribbon 做負(fù)載均衡的話,他還有一層緩存每隔 30 秒同步一次

如果說一個(gè)服務(wù)的正常下線,極端的情況這個(gè)時(shí)間應(yīng)該就是 30+30+30+30 差不多 120 秒的時(shí)間了。

如果服務(wù)非正常下線,還需要靠每 60 秒執(zhí)行一次的清理線程去剔除超過 90 秒沒有心跳的服務(wù),那么這里的極端情況可能需要 3 次 60秒才能檢測出來,就是 180 秒的時(shí)間。

累計(jì)可能最長的感知時(shí)間就是:180 + 120 = 300 秒,5分鐘的時(shí)間。

解決方案當(dāng)然就是改這些時(shí)間。

修改 ribbon 同步緩存的時(shí)間為 3 秒:ribbon.ServerListRefreshInterval = 3000

修改客戶端同步緩存時(shí)間為 3 秒 :eureka.client.registry-fetch-interval-seconds = 3

心跳間隔時(shí)間修改為 3 秒:eureka.instance.lease-renewal-interval-in-seconds = 3

超時(shí)剔除的時(shí)間改為 9 秒:eureka.instance.lease-expiration-duration-in-seconds = 9

清理線程定時(shí)時(shí)間改為 5 秒執(zhí)行一次:eureka.server.eviction-interval-timer-in-ms = 5000

同步到只讀緩存的時(shí)間修改為 3 秒一次:eureka.server.response-cache-update-interval-ms = 3000

如果按照這個(gè)時(shí)間參數(shù)設(shè)置讓我們重新計(jì)算可能感知到服務(wù)下線的最大時(shí)間:

正常下線就是 3+3+3+3=12 秒,非正常下線再加 15 秒為 27 秒。

結(jié)束

OK,關(guān)于 Spring Boot 服務(wù)的啟動、下線時(shí)間的優(yōu)化就聊到這里,但是我認(rèn)為服務(wù)拆分足夠好,代碼寫的更好一點(diǎn),這些問題可能都不是問題了。

以上就是Spring Boot優(yōu)化后啟動速度快到飛起技巧示例的詳細(xì)內(nèi)容,更多關(guān)于Spring Boot啟動優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot整合EasyCaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能

    SpringBoot整合EasyCaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能

    這篇文章主要介紹了SpringBoot整合EasyCaptcha實(shí)現(xiàn)圖形驗(yàn)證碼功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-02-02
  • Mybatis?一級緩存和二級緩存原理區(qū)別

    Mybatis?一級緩存和二級緩存原理區(qū)別

    這篇文章主要介紹了Mybatis?一級緩存和二級緩存原理區(qū)別?,文章通過圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-09-09
  • Java應(yīng)用服務(wù)器之tomcat部署的詳細(xì)教程

    Java應(yīng)用服務(wù)器之tomcat部署的詳細(xì)教程

    這篇文章主要介紹了Java應(yīng)用服務(wù)器之tomcat部署,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java中switch判斷語句典型使用實(shí)例

    Java中switch判斷語句典型使用實(shí)例

    這篇文章主要介紹了Java中switch判斷語句典型使用實(shí)例,本文直接給出代碼實(shí)例,在忘記switch語法時(shí)特別有用,復(fù)制修改即可使用,需要的朋友可以參考下
    2015-06-06
  • Spring的定時(shí)任務(wù)@Scheduled源碼詳解

    Spring的定時(shí)任務(wù)@Scheduled源碼詳解

    這篇文章主要介紹了Spring的定時(shí)任務(wù)@Scheduled源碼詳解,@Scheduled注解是包org.springframework.scheduling.annotation中的一個(gè)注解,主要是用來開啟定時(shí)任務(wù),本文提供了部分實(shí)現(xiàn)代碼與思路,需要的朋友可以參考下
    2023-09-09
  • MyBatis逆向工程基本操作及代碼實(shí)例

    MyBatis逆向工程基本操作及代碼實(shí)例

    這篇文章主要介紹了MyBatis逆向工程基本操作及代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java 入門圖形用戶界面設(shè)計(jì)之事件處理上

    Java 入門圖形用戶界面設(shè)計(jì)之事件處理上

    圖形界面(簡稱GUI)是指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面。與早期計(jì)算機(jī)使用的命令行界面相比,圖形界面對于用戶來說在視覺上更易于接受,本篇精講Java語言中關(guān)于圖形用戶界面的事件處理
    2022-02-02
  • mac 安裝java1.8的過程詳解

    mac 安裝java1.8的過程詳解

    這篇文章主要介紹了mac 安裝java1.8,包括下載過程及配置環(huán)境相關(guān)知識介紹,本文結(jié)合實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-09-09
  • JavaFx實(shí)現(xiàn)登錄成功跳轉(zhuǎn)到程序主頁面

    JavaFx實(shí)現(xiàn)登錄成功跳轉(zhuǎn)到程序主頁面

    這篇文章主要為大家詳細(xì)介紹了JavaFx實(shí)現(xiàn)登錄成功跳轉(zhuǎn)到程序主頁面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • SpringBoot使用@Cacheable注解實(shí)現(xiàn)緩存功能流程詳解

    SpringBoot使用@Cacheable注解實(shí)現(xiàn)緩存功能流程詳解

    最近一直再學(xué)Spring Boot,在學(xué)習(xí)的過程中也有過很多疑問。為了解答自己的疑惑,也在網(wǎng)上查了一些資料,以下是對@Cacheable注解的一些理解
    2023-01-01

最新評論

竹溪县| 岑巩县| 轮台县| 田林县| 古丈县| 滦南县| 云安县| 定西市| 衡阳市| 蒲江县| 阿巴嘎旗| 赫章县| 沅陵县| 饶平县| 汉中市| 岳西县| 东安县| 新邵县| 普兰县| 浑源县| 定州市| 呼图壁县| 桃江县| 漯河市| 红安县| 新闻| 五台县| 新竹市| 台东市| 永福县| 绥滨县| 鄂尔多斯市| 晴隆县| 南充市| 佛学| 黑山县| 清镇市| 福鼎市| 福泉市| 蚌埠市| 安国市|