免费爱碰视频在线观看,九九精品国产屋,欧美亚洲尤物久久精品,1024在线观看视频亚洲

      一些可以顯著提高大型 Java 項(xiàng)目啟動(dòng)速度的嘗試

      一些可以顯著提高大型 Java 項(xiàng)目啟動(dòng)速度的嘗試

      我們線(xiàn)上的業(yè)務(wù) jar 包基本上普遍比較龐大,動(dòng)不動(dòng)一個(gè) jar 包上百 M,啟動(dòng)時(shí)間在分鐘級(jí),拖慢了我們?cè)诠收蠒r(shí)快速擴(kuò)容的響應(yīng)。于是做了一些分析,看看 Java 程序啟動(dòng)慢到底慢在哪里,如何去優(yōu)化,目前的效果是大部分大型應(yīng)用啟動(dòng)時(shí)間可以縮短 30%~50%

      主要有下面這些內(nèi)容

      • 修改 async-profiler 源碼,只抓取啟動(dòng)階段 main 線(xiàn)程的 wall 時(shí)間火焰圖( )
      • 重新實(shí)現(xiàn) JarIndex( )
      • 結(jié)合 JarIndex 重新自定義類(lèi)加載器,啟動(dòng)提速 30%+( )
      • SpringBean 加載耗時(shí) timeline 可視化分析( )
      • SpringBean 的可視化依賴(lài)分析( )
      • 基于依賴(lài)拓?fù)涞?SpringBean 的異步加載( )

      無(wú)觀(guān)測(cè)不優(yōu)化

      秉承著無(wú)觀(guān)測(cè)不優(yōu)化的想法,首先我們要知道啟動(dòng)慢到底慢在了哪里。我之前分享過(guò)很多次關(guān)于火焰圖的使用,結(jié)果很多人遇到問(wèn)題就開(kāi)始考慮火焰圖,但是一個(gè)啟動(dòng)慢其實(shí)是一個(gè)時(shí)序問(wèn)題,不是一個(gè) hot CPU 熱點(diǎn)問(wèn)題。很多時(shí)候慢,不一定是 cpu 占用過(guò)高,很有可能是等鎖、等 IO 或者傻傻的 sleep。

      在 Linux 中有一個(gè)殺手級(jí)的工具 bootchart 來(lái)分析 linux 內(nèi)核啟動(dòng)的問(wèn)題,它把啟動(dòng)過(guò)程中所有的 IO、CPU 占用情況都做了詳細(xì)的劃分,我們可以很清楚地看到各個(gè)時(shí)間段,時(shí)間耗在了哪里,基于這個(gè) chart,你就可以看看哪些過(guò)程可以延后處理、異步處理等。

      在 Java 中,暫時(shí)沒(méi)有類(lèi)似的工具,但是又想知道時(shí)間到底耗在了哪里要怎么做呢,至少大概知道耗在了什么地方。在生成熱點(diǎn)調(diào)用火焰圖的時(shí)候,我們通過(guò) arthas 的幾個(gè)簡(jiǎn)單的命令就可以生成,它底層用的是 async-profiler 這個(gè)開(kāi)源項(xiàng)目,它的作者 apangin 做過(guò)一系列關(guān)于 jvm profiling 相關(guān)的分享,感興趣的同學(xué)可以去看看。

      async-profiler 底層原理簡(jiǎn)介

      async-profiler 是一個(gè)非常強(qiáng)大的工具,使用 jvmti 技術(shù)來(lái)實(shí)現(xiàn)。它的 NB 之處在于它利用了 libjvm.so 中 JVM 內(nèi)部的 API AsyncGetCallTrace 來(lái)獲取 Java 函數(shù)堆棧,精簡(jiǎn)后的偽代碼如下:

      static bool vm_init(JavaVM *vm) { std::cout << "vm_init" << std::endl; // 從 libjvm.so 中獲取 AsyncGetCallTrace 的函數(shù)指針句柄 void *libjvm = dlopen("libjvm.so", RTLD_LAZY); _asyncGetCallTrace = (AsyncGetCallTrace) dlsym(libjvm, "AsyncGetCallTrace");}// 事件回調(diào)void recordSample(void *ucontext, uint64_t counter, jint event_type, Event *event) { std::cout << "Profiler::recordSample: " << std::endl; ASGCT_CallFrame frames[maxFramesToCapture]; ASGCT_CallTrace trace; trace.frames = frames; trace.env = getJNIEnv(g_jvm); // 調(diào)用 AsyncGetCallTrace 獲取堆棧 _asyncGetCallTrace(&trace, maxFramesToCapture, ucontext);}

      你可能要說(shuō)獲取個(gè)堆棧還需要搞這么復(fù)雜,jstack 等工具不是實(shí)現(xiàn)得很好了嗎?其實(shí)不然。

      jstack 等工具獲取函數(shù)堆棧需要 jvm 進(jìn)入到 safepoint,對(duì)于采樣非常頻繁的場(chǎng)景,會(huì)嚴(yán)重的影響 jvm 的性能,具體的原理不是本次內(nèi)容的重點(diǎn)這里先不展開(kāi)。

      async-profiler 除了可以生成熱點(diǎn)調(diào)用的火焰圖,它還提供了 Wall-clock profiling 的功能,這個(gè)功能其實(shí)就是固定時(shí)間采樣所有的線(xiàn)程(不管線(xiàn)程當(dāng)前是 Running、Sleeping 還是 Blocked),它在文檔中也提到了,這種方式的 profiling 適合用來(lái)分析應(yīng)用的啟動(dòng)過(guò)程,我們姑且用這個(gè)不太精確的方式來(lái)粗略測(cè)量啟動(dòng)階段耗時(shí)在了哪些函數(shù)里。

      但是這個(gè)工具會(huì)抓取所有的線(xiàn)程的堆棧,按這樣的方式抓取的 wall-clock 火焰圖沒(méi)法看,不信你看。

      就算你找到了 main 線(xiàn)程,在函數(shù)耗時(shí)算占比的時(shí)候也不太方便,我們關(guān)心的其實(shí)只是 main 線(xiàn)程(也就是加載 jar 包,執(zhí)行 spring 初始化的線(xiàn)程),于是我做了一些簡(jiǎn)單的修改,讓 async-profiler 只取抓取 main 線(xiàn)程的堆棧。

      重新編譯運(yùn)行

      java -agentpath:/path/to/libasyncProfiler.so=start,event=wall,interval=1ms,threads,file=profile.html-jar xxx.jar

      這樣生成的火焰圖就清爽多了,這樣就知道時(shí)間耗在了什么函數(shù)上。

      接下來(lái)就是分析這個(gè) wall-clock 的火焰圖,點(diǎn)開(kāi)幾個(gè)調(diào)用棧仔細(xì)分析,發(fā)現(xiàn)很多時(shí)間花費(fèi)在類(lèi)和資源文件查找和加載(挺失望的,java 連這部分都做不好)

      繼續(xù)分析代碼看看類(lèi)加載在做什么。

      Java 垃圾般實(shí)現(xiàn)的類(lèi)查找加載

      Java 地類(lèi)加載不出意外最終都走到了 java.net.URLClassLoader#findClass 這里。

      這里的 ucp 指的是 URLClassPath,也就是 classpath 路徑的集合。對(duì)于 SpringBoot 的應(yīng)用來(lái)說(shuō),classpath 已經(jīng)在 META-INF 里寫(xiě)清楚了。

      Spring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/

      此次測(cè)試的程序 BOOT-INF/lib/ 有 300 多個(gè)依賴(lài)的 jar 包,當(dāng)加載某個(gè)類(lèi)時(shí),除了 BOOT-INF/classes/ 之外 Java 居然要遍歷那 300 個(gè) jar 包去查看這些 jar 包中是否包含某個(gè)類(lèi)。

      我在 loader.getResource 上注入了一下打印,看看這些函數(shù)調(diào)用了多少次。

      可以看到太喪心病狂了,加載一個(gè)類(lèi),居然要調(diào)用 loader.getResource 去 jar 包中嘗試幾百次。我就按二分之一 150 來(lái)算,如果加載一萬(wàn)個(gè)類(lèi),要調(diào)用這個(gè)函數(shù) 150W 次。

      請(qǐng)忽略源碼中的 LookupCache 特性,這個(gè)特性看起來(lái)是為了加速 jar 包查找的,但是這個(gè)特性看源碼是一個(gè) oracle 商業(yè)版的才有的特性,在目前的 jdk 中是無(wú)法啟用的。(推測(cè),如果理解不對(duì)請(qǐng)告知我)

      于是有了一些粗淺的想法,為何不告訴 java 這個(gè)類(lèi)在那個(gè) jar 里?做索引這么天然的想法為什么不實(shí)現(xiàn)。

      以下面為例,項(xiàng)目依賴(lài)三個(gè) jar 包,foo.jar、bar.jar、baz.jar,其中分別包含了特定包名的類(lèi),理想情況下我們可以生成一個(gè)索引文件,如下所示。

      foo.jarcom/foo1com/foo2bar.jarcom/barcom/bar/barbarbaz.jarcom/baz

      這就是我們接下來(lái)要介紹的 JarIndex 技術(shù)。

      JarIndex 技術(shù)

      其實(shí) Jar 在文件格式上是支持索引技術(shù)的,稱(chēng)為 JarIndex,通過(guò) jar -i 就可以在 META-INF/ 目錄下生成 INDEX.LIST 文件。別高興的太早,這個(gè) JarIndex 目前無(wú)法真正起到作用,有下面幾個(gè)原因:

      • INDEX.LIST 文件生成不正確,尤其是目前最流行的 fatjar 中包含 jar 列表的情況
      • classloader 不支持(那不是白忙活嗎)

      首先來(lái)看 INDEX.LIST 文件生成不正確的問(wèn)題,隨便拿一個(gè) jar 文件,使用 jar -i 生成一下試試。

      JarIndex-Version: 1.0encloud-api_origin.jarBOOT-INFBOOT-INF/classesBOOT-INF/classes/comBOOT-INF/classes/com/encloud….META-INFMETA-INF/mavenMETA-INF/maven/com.encloudMETA-INF/maven/com.encloud/encloud-apiBOOT-INF/liborgorg/springframeworkorg/springframework/bootorg/springframework/boot/loaderorg/springframework/boot/loader/jarorg/springframework/boot/loader/dataorg/springframework/boot/loader/archiveorg/springframework/boot/loader/util

      可以看到在 BOOT-INF/lib 目錄中的類(lèi)索引并沒(méi)有在這里生成,這里面可是有 300 多個(gè) jar 包。

      同時(shí)生成不對(duì)的地方還有,org 目錄下只有文件夾并沒(méi)有 class 文件,org 這一行不應(yīng)該在 INDEX.LIST 文件中。

      第二個(gè)缺陷才是最致命的,目前的 classloader 不支持 JarIndex 這個(gè)特性。

      所以我們要做兩個(gè)事情,生成正確的 JarIndex,同時(shí)修改 SpringBoot 的 classloader 讓其支持 JarIndex。

      生成正確的 JarIndex

      這個(gè)簡(jiǎn)單,就是遍歷 jar 包里的類(lèi),將其所在的包名抽取出來(lái)。SpringBoot 應(yīng)用有三個(gè)地方存放了 class:

      • BOOT-INF/classes
      • BOOT-INF/lib
      • jar 包根目錄下 org/springframework/boot/loader

      生成的時(shí)候需要考慮到上面的情況,剩下的就簡(jiǎn)單了。遍歷這些目錄,將所有的包含 class 文件的包名過(guò)濾過(guò)來(lái)就行。

      大概生成的結(jié)果是:

      JarIndex-Version: 1.0encloud-api.jar/BOOT-INF/classescom/encloudcom/encloud/app/controllercom/encloud/app/controller/v2/org/springframework/boot/loaderorg/springframework/boot/loader/archiveorg/springframework/boot/loader/dataorg/springframework/boot/loader/jarorg/springframework/boot/loader/util/BOOT-INF/lib/spring-core-4.3.9.RELEASE.jarorg/springframework/asmorg/springframework/cgliborg/springframework/cglib/beansorg/springframework/cglib/core/BOOT-INF/lib/guava-19.0.jarcom/google/common/annotationscom/google/common/basecom/google/common/base/internalcom/google/common/cache… other jar …

      除了加載類(lèi)需要查找,其實(shí)還有不少資源文件需要查找,比如 spi 等掃描過(guò)程中需要,順帶把資源文件的索引也生成一下寫(xiě)入到 RES_INDEX.LIST 中,原理類(lèi)似,這里展開(kāi)。

      自定義 classloder

      生成了 INDEX.LIST 文件,接下來(lái)就是要實(shí)現(xiàn)了一個(gè) classloader 能支持一步到位通過(guò)索引文件去對(duì)應(yīng)的 jar 包中去加載 class,核心的代碼如下:

      public class JarIndexLaunchedURLClassLoader extends URLClassLoader { public JarIndexLaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) { super(urls, parent); initJarIndex(urls); // 根據(jù) INDEX.LIST 創(chuàng)建包名到 jar 文件的映射關(guān)系 } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class loadedClass = findLoadedClass(name); if (loadedClass != null) return loadedClass; // 如果是 loader 相關(guān)的類(lèi),則直接加載,不用找了,就在 jar 包的根目錄下 if (name.startsWith(“org.springframework.boot.loader.”) || name.startsWith(“com.seewo.psd.bootx.loader.”)) { Class result = loadClassInLaunchedClassLoader(name); if (resolve) { resolveClass(result); } return result; } // skip java.*, org.w3c.dom.* com.sun.* ,這些包交給 java 默認(rèn)的 classloader 去處理 if (!name.startsWith(“java”) && !name.contains(“org.w3c.dom.”) && !name.contains(“xml”) && !name.startsWith(“com.sun”)) { int lastDot = name.lastIndexOf(‘.’); if (lastDot >= 0) { String packageName = name.substring(0, lastDot); String packageEntryName = packageName.replace(‘.’, ‘/’); String path = name.replace(‘.’, ‘/’).concat(“.class”); // 通過(guò) packageName 找到對(duì)應(yīng)的 jar 包 List loaders = package2LoaderMap.get(packageEntryName); if (loaders != null) { for (JarFileResourceLoader loader : loaders) { ClassSpec classSpec = loader.getClassSpec(path); // 從 jar 包中讀取文件 if (classSpec == null) { continue; } // 文件存在,則加載這個(gè) class Class definedClass = defineClass(name, classSpec.getBytes(), 0, classSpec.getBytes().length, classSpec.getCodeSource()); definePackageIfNecessary(name); return definedClass; } } } } // 執(zhí)行到這里,說(shuō)明需要父類(lèi)加載器來(lái)加載類(lèi)(兜底) definePackageIfNecessary(name); return super.loadClass(name, resolve); }}

      到這里我們基本上就實(shí)現(xiàn)了一個(gè)支持 JarIndex 的類(lèi)加載器,這里的改動(dòng)經(jīng)實(shí)測(cè)效果已經(jīng)效果非常明顯。

      除此之外,我還發(fā)現(xiàn)查找一個(gè)已加載的類(lèi)是一個(gè)非常高頻執(zhí)行的操作,于是可以在 JarIndexLaunchedURLClassLoader 之前再加一層緩存(思想來(lái)自 sofa-boot)

      public class CachedLaunchedURLClassLoader extends JarIndexLaunchedURLClassLoader { private final Map classCache = new ConcurrentHashMap(3000); @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { return loadClassWithCache(name, resolve); } private Class loadClassWithCache(String name, boolean resolve) throws ClassNotFoundException { LoadClassResult result = classCache.get(name); if (result != null) { if (result.getEx() != null) { throw result.getEx(); } return result.getClazz(); } try { Class clazz = super.findLoadedClass(name); if (clazz == null) { clazz = super.loadClass(name, resolve); } if (clazz == null) { classCache.put(name, LoadClassResult.NOT_FOUND); } return clazz; } catch (ClassNotFoundException exception) { classCache.put(name, new LoadClassResult(exception)); throw exception; }}

      注意:這里為了簡(jiǎn)單示例直接用 ConcurrentHashMap 來(lái)緩存 class,更好的做法是用 guava-cache 等可以帶過(guò)期淘汰的 map,避免類(lèi)被永久緩存。

      如何不動(dòng) SpringBoot 的代碼實(shí)現(xiàn) classloader 的替換

      接下的一個(gè)問(wèn)題是如何不修改 SpringBoot 的情況下,把 SpringBoot 的 Classloader 替換為我們寫(xiě)的呢?

      大家都知道,SpringBoot 的 jar 包啟動(dòng)類(lèi)其實(shí)并不是我們項(xiàng)目中寫(xiě)的 main 函數(shù),其實(shí)是

      org.springframework.boot.loader.JarLauncher,這個(gè)類(lèi)才是真正的 jar 包的入口。

      package org.springframework.boot.loader;public class JarLauncher extends ExecutableArchiveLauncher {public static void main(String[] args) throws Exception {new JarLauncher().launch(args);}}

      那我們只要替換這個(gè)入口類(lèi)就可以接管后面的流程了。如果只是替換那很簡(jiǎn)單,修改生成好的 jar 包就可以了,但是這樣后面維護(hù)的成本比較高,如果在打包的時(shí)候就替換就好了。SpringBoot 的打包是用 spring-boot-maven-plugin 插件

      org.springframework.boot spring-boot-maven-plugin

      最終生成的 META-INF/MANIFEST.MF 文件如下

      $ cat META-INF/MANIFEST.MFManifest-Version: 1.0Implementation-Title: encloud-apiImplementation-Version: 2.0.0-SNAPSHOTArchiver-Version: Plexus ArchiverBuilt-By: arthurImplementation-Vendor-Id: com.encloudSpring-Boot-Version: 1.5.4.RELEASEImplementation-Vendor: Pivotal Software, Inc.Main-Class: org.springframework.boot.loader.JarLauncherStart-Class: com.encloud.APIBootSpring-Boot-Classes: BOOT-INF/classes/Spring-Boot-Lib: BOOT-INF/lib/Created-By: Apache Maven 3.8.5Build-Jdk: 1.8.0_332Implementation-URL: http://projects.spring.io/spring-boot/parent/enclo ud-api/

      為了實(shí)現(xiàn)我們的需求,就要看 spring-boot-maven-plugin 這個(gè)插件到底是如何寫(xiě)入 Main-Class 這個(gè)類(lèi)的,經(jīng)過(guò)漫長(zhǎng)的 maven 插件源碼的調(diào)試,發(fā)現(xiàn)這個(gè)插件居然提供了擴(kuò)展點(diǎn),可以支持修改 Main-Class,它提供了一個(gè) layoutFactory 可以自定義

      org.springframework.boot spring-boot-maven-plugin repackage com.seewo.psd.bootx bootx-loader-tools 0.1.1

      實(shí)現(xiàn)這個(gè)

      package com.seewo.psd.bootx.loader.tools;import org.springframework.boot.loader.tools.*;import java.io.File;import java.io.IOException;import java.util.Locale;public class MyLayoutFactory implements LayoutFactory { private static final String NESTED_LOADER_JAR = “META-INF/loader/spring-boot-loader.jar”; private static final String NESTED_LOADER_JAR_BOOTX = “META-INF/loader/bootx-loader.jar”; public static class Jar implements RepackagingLayout, CustomLoaderLayout { @Override public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException { // 拷貝 springboot loader 相關(guān)的文件到 jar 根目錄 writer.writeLoaderClasses(NESTED_LOADER_JAR); // 拷貝 bootx loader 相關(guān)的文件到 jar 根目錄 writer.writeLoaderClasses(NESTED_LOADER_JAR_BOOTX); } @Override public String getLauncherClassName() { // 替換為我們自己的 JarLauncher return “com.seewo.psd.bootx.loader.JarLauncher”; } }}

      接下來(lái)實(shí)現(xiàn)我們自己的 JarLauncher

      package com.seewo.psd.bootx.loader;import java.net.URL;public class JarLauncher extends org.springframework.boot.loader.JarLauncher { @Override protected ClassLoader createClassLoader(URL[] urls) throws Exception { return new CachedLaunchedURLClassLoader(urls, getClass().getClassLoader()); } public static void main(String[] args) throws Exception { new JarLauncher().launch(args); }}

      重新編譯就可以實(shí)現(xiàn)替換

      $ cat META-INF/MANIFEST.MFManifest-Version: 1.0…Main-Class: com.seewo.psd.bootx.loader.JarLauncher…

      到這里,我們就基本完成所有的工作,不用改一行業(yè)務(wù)代碼,只用改幾行 maven 打包腳本,就可以實(shí)現(xiàn)支持 JarIndex 的類(lèi)加載實(shí)現(xiàn)。

      優(yōu)化效果

      我們來(lái)看下實(shí)際的效果,項(xiàng)目 1 稍微小型一點(diǎn),啟動(dòng)耗時(shí)從 70s 降低到 46s

      第二個(gè) jar 包更大一點(diǎn),效果更明顯,啟動(dòng)耗時(shí)從 220s 減少到 123s

      未完待續(xù)

      其實(shí)優(yōu)化到這里,還遠(yuǎn)遠(yuǎn)沒(méi)有達(dá)到我想要的目標(biāo),為什么啟動(dòng)需要這么長(zhǎng)時(shí)間,解決了類(lèi)查找的問(wèn)題,那我們來(lái)深挖一下 Spring 的初始化。

      Spring bean 的初始化是串行進(jìn)行的,于是我先來(lái)做一個(gè)可視化 timeline,看看到底是哪些 Bean 耗時(shí)很長(zhǎng)。

      Spring Bean 初始化時(shí)序可視化

      因?yàn)椴粫?huì)寫(xiě)前端,這里偷一下懶,利用 APM 的工具,把數(shù)據(jù)上報(bào)到 jaeger,這樣我們就可以得到一個(gè)包含調(diào)用關(guān)系的timeline 的界面了。jaeger 的網(wǎng)址在這里:www.jaegertracing.io/

      首先我們繼承 DefaultListableBeanFactory 來(lái)對(duì) createBean 的過(guò)程做記錄。

      public class BeanLoadTimeCostBeanFactory extends DefaultListableBeanFactory { private static ThreadLocal parentStackThreadLocal = new ThreadLocal(); @Override protected Object createBean(String beanName, RootBeanDefinition rbd, Object[] args) throws BeanCreationException { // 記錄 bean 初始化開(kāi)始 Object object = super.createBean(beanName, rbd, args); // 記錄 bean 初始化結(jié)束 return object; }

      接下來(lái)我們實(shí)現(xiàn) ApplicationContextInitializer,在 initialize 方法中替換 beanFactory 為我們自己寫(xiě)的。

      public class BeanLoadTimeCostApplicationContextInitializer implements ApplicationContextInitializer, Ordered { public BeanLoadCostApplicationContextInitializer() { System.out.println(“in BeanLoadCostApplicationContextInitializer()”); } @Override public void initialize(ConfigurableApplicationContext applicationContext) { if (applicationContext instanceof GenericApplicationContext) { System.out.println(“BeanLoadCostApplicationContextInitializer run”); BeanLoadTimeCostBeanFactory beanFactory = new BeanLoadTimeCostBeanFactory(); Field field = GenericApplicationContext.class.getDeclaredField(“beanFactory”); field.setAccessible(true); field.set(applicationContext, beanFactory); } }}

      接下來(lái)將記錄的狀態(tài)上報(bào)到 jaeger 中,實(shí)現(xiàn)可視化堆棧顯示。

      public void reportBeanCreateResult(BeanCreateResult beanCreateResult) { Span span = GlobalTracer.get().buildSpan(beanCreateResult.getBeanClassName()).withStartTimestamp(beanCreateResult.getBeanStartTime() * 1000).start(); try (Scope ignore = GlobalTracer.get().scopeManager().activate(span)) { for (BeanCreateResult item : beanCreateResult.getChildren()) { Span childSpan = GlobalTracer.get().buildSpan(item.getBeanClassName()).withStartTimestamp(item.getBeanStartTime() * 1000).start(); try (Scope ignore2 = GlobalTracer.get().scopeManager().activate(childSpan)) { printBeanStat(item); } finally { childSpan.finish(item.getBeanEndTime() * 1000); } } } finally { span.finish(beanCreateResult.getBeanEndTime() * 1000); }}

      通過(guò)這種方式,我們可以很輕松的看到 spring 啟動(dòng)階段 bean 加載的 timeline,生成的圖如下所示。

      這對(duì)我們進(jìn)一步優(yōu)化 bean 的加載提供了思路,可以看到 bean 的依賴(lài)關(guān)系和加載耗時(shí)具體耗在了哪個(gè) bean。通過(guò)這種方式可以在 SpringBean 串行加載的前提下,把 bean 的加載盡可能的優(yōu)化。

      SpringBean 的依賴(lài)分析

      更好一點(diǎn)的方案是基于 SpringBean 的依賴(lài)關(guān)系做并行加載。這個(gè)特性 2011 年前就有人提給了 Spring,具體看這個(gè) issue:github.com/spring-proj…

      就在去年,還有人去這個(gè) issue 下去恭祝這個(gè) issue 10 周年快樂(lè)。

      做并行加載確實(shí)有一些難度,真實(shí)項(xiàng)目的 Spring Bean 依賴(lài)關(guān)系非常復(fù)雜,我把 Spring Bean 的依賴(lài)關(guān)系導(dǎo)入到 neo4j 圖數(shù)據(jù)庫(kù),然后進(jìn)行查詢(xún)

      MATCH (n)RETURN n;

      得到的圖如下所示。一方面 Bean 的數(shù)量特別多,還有復(fù)雜的依賴(lài)關(guān)系,以及循環(huán)依賴(lài)。

      基于此依賴(lài)關(guān)系,我們是有機(jī)會(huì)去做 SpringBean 的并行加載的,這部分還沒(méi)實(shí)現(xiàn),希望后面有機(jī)會(huì)可以完整的實(shí)現(xiàn)這塊的邏輯,個(gè)人感覺(jué)可以做到 10s 內(nèi)啟動(dòng)完一個(gè)超大的項(xiàng)目。

      Java 啟動(dòng)優(yōu)化的其它技術(shù)

      Java 啟動(dòng)的其它技術(shù)還有 Heap Archive、CDS,以及 GraalVM 的 AOT 編譯,不過(guò)這幾個(gè)技術(shù)目前都有各自的缺陷,還無(wú)法完全解決目前我們遇到的問(wèn)題。

      后記

      這篇文章中用到的技術(shù)只是目前比較粗淺的嘗試,如果大家有更好的優(yōu)化,可以跟我交流,非常感謝。

      作者:挖坑的張師傅鏈接:https://juejin.cn/post/7117815437559070734

      鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
      用戶(hù)投稿
      上一篇 2022年7月11日 21:09
      下一篇 2022年7月11日 21:09

      相關(guān)推薦

      • 我國(guó)首臺(tái)130噸級(jí)重復(fù)使用液氧煤油補(bǔ)燃循環(huán)發(fā)動(dòng)機(jī)試車(chē)成功

        新華社西安11月26日電記者26日從中國(guó)航天科技集團(tuán)六院獲悉,由該院自主研制的首臺(tái)130噸級(jí)重復(fù)使用液氧煤油補(bǔ)燃循環(huán)發(fā)動(dòng)機(jī)兩次起動(dòng)試車(chē)取得圓滿(mǎn)成功。 該型發(fā)動(dòng)機(jī)是瞄準(zhǔn)我國(guó)新一代運(yùn)載…

        2022年11月27日
      • 世界領(lǐng)先!我國(guó)已應(yīng)用于新一代戰(zhàn)機(jī)→

        本文轉(zhuǎn)自【央視軍事】; “3D打印技術(shù)在飛機(jī)上的應(yīng)用 我們已達(dá)到規(guī)模化、工程化 處于世界領(lǐng)先位置” 如何運(yùn)用3D打印設(shè)備 生產(chǎn)新一代戰(zhàn)機(jī)的零部件? 規(guī)?;?工程化 3D打印件批量裝…

        2022年11月27日
      • 30個(gè)無(wú)加盟費(fèi)的項(xiàng)目(茶顏悅色奶茶店加盟費(fèi)多少)

        茶顏悅色又爆了,8月18日,茶顏悅色南京門(mén)店正式開(kāi)業(yè),開(kāi)張不到半小時(shí),門(mén)店就人滿(mǎn)為患,消費(fèi)者的購(gòu)買(mǎi)熱情十分高漲,而由于人流量過(guò)大造成擁堵,茶顏悅色也不得不暫停營(yíng)業(yè)。 當(dāng)然,這里面排…

        2022年11月27日
      • 凈利潤(rùn)率越高越好嗎(凈利潤(rùn)率多少合適)

        一、持續(xù)增收不增利,平均凈利潤(rùn)率首次跌入個(gè)位數(shù) 2021年,增收不增利依舊是行業(yè)主流。具體來(lái)看,大部分企業(yè)營(yíng)業(yè)收入呈增長(zhǎng)態(tài)勢(shì),E50企業(yè)平均同比增速達(dá)到17.3%,但是利潤(rùn)增速則明…

        2022年11月26日
      • 5+3疫情防控從哪天開(kāi)始算(遼寧疫情防控最新政策)

        最近有關(guān)國(guó)內(nèi)各地的疫情大家也都有在持續(xù)關(guān)注,目前國(guó)內(nèi)各地疫情隔離時(shí)間也根據(jù)二十條防控措施有了新的調(diào)整。那么,5+3疫情防控從哪天開(kāi)始算?對(duì)于密接的5+3隔離時(shí)間計(jì)算大家還是比較關(guān)心…

        2022年11月25日
      • 藍(lán)碼怎么變綠碼需要幾天(藍(lán)碼怎么變綠碼需要幾天)

        大家都知道健康碼的顏色有紅碼、綠碼、黃碼,近日湖南健康碼上線(xiàn)“藍(lán)碼”,不少小伙伴發(fā)現(xiàn)自己健康碼變藍(lán)了,都想趕緊恢復(fù)綠碼,那么藍(lán)碼怎么變綠碼需要幾天?下面小編為大家?guī)?lái)藍(lán)碼變綠碼需要…

        2022年11月25日
      • 寶可夢(mèng)朱紫野怪對(duì)應(yīng)努力值表 野怪對(duì)應(yīng)努力值查詢(xún)一覽圖

        寶可夢(mèng)朱紫野怪對(duì)應(yīng)努力值是多少?不同的野怪對(duì)應(yīng)的努力值不一樣,因此不少玩家對(duì)于野怪對(duì)應(yīng)努力值的詳情不太了解,今天我們就來(lái)看一看野怪對(duì)應(yīng)努力值表的具體內(nèi)容,小編已經(jīng)將詳情分享在下面,…

        2022年11月25日
      • 規(guī)范透明促PPP高質(zhì)量發(fā)展——16萬(wàn)億元大市場(chǎng)迎來(lái)新規(guī)

        近日,財(cái)政部印發(fā)《關(guān)于進(jìn)一步推動(dòng)政府和社會(huì)資本合作(PPP)規(guī)范發(fā)展、陽(yáng)光運(yùn)行的通知》,從做好項(xiàng)目前期論證、推動(dòng)項(xiàng)目規(guī)范運(yùn)作、嚴(yán)防隱性債務(wù)風(fēng)險(xiǎn)、保障項(xiàng)目陽(yáng)光運(yùn)行四個(gè)方面進(jìn)一步規(guī)范P…

        2022年11月25日
      • 拼多多百億補(bǔ)貼預(yù)售一般多久發(fā)貨(拼多多百億補(bǔ)貼預(yù)售)

        拼多多里面有很多優(yōu)惠活動(dòng),其中百億補(bǔ)貼活動(dòng)非?;鸨?,一些里面的東西價(jià)格比別的平臺(tái)便宜,質(zhì)量也有保障,還有預(yù)售的活動(dòng),那么拼多多百億補(bǔ)貼預(yù)售一般多久發(fā)貨?下面小編為大家?guī)?lái)拼多多百億…

        2022年11月25日
      • 北京疫情多久能解除封控(北京疫情還要多久結(jié)束)

        最近一段時(shí)間北京疫情形勢(shì)備受關(guān)注,馬上就要到年底了,不少人想要去北京辦事,。都非常關(guān)注當(dāng)?shù)匾咔橄嚓P(guān)政策,那么 北京疫情多久能解除封控?北京疫情什么時(shí)候恢復(fù)正常生活?下面小編為大家?guī)А?/p>

        2022年11月25日

      聯(lián)系我們

      聯(lián)系郵箱:admin#wlmqw.com
      工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息