记一次SpringBoot启动优化实践

 背景

最近看到一本关于 Java 应用通过基础设施于工具的改进,实现从构建到启动全方面提速的小册。想着自己目前在做的项目最让我觉得不爽的就是项目启动。尤其是需要频繁改代码,频繁重启项目的时候,每次项目启动起码都要两分钟才能启动完毕,挺影响开发效率的。就想着结合看的小册来做一个项目的启动优化,至少让自己在开发中更流畅

可能有人会说,可以使用devTools 来做热部署。对这玩意没有太了解就没有用

项目以及各项参数

SpringBoot 版本:1.5.6

机器内存:16G

JVM参数:默认值

AspectJ版本:1.8.8

启动整体耗时:150s~160s

优化结果

优化前

 2023-01-03 20:04:11.187 CST [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Documentation plugins bootstrapped
 2023-01-03 20:04:11.189 CST [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)
 2023-01-03 20:04:11.210 CST [main] INFO  s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
 2023-01-03 20:04:11.321 CST [main] INFO  com.xxx.XXApplication - Started XXApplication in 158.913 seconds (JVM running for 160.755)
复制代码

优化后

 2023-01-03 20:04:11.187 CST [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Documentation plugins bootstrapped
 2023-01-03 20:04:11.189 CST [main] INFO  s.d.s.w.p.DocumentationPluginsBootstrapper - Found 1 custom documentation plugin(s)
 2023-01-03 20:04:11.210 CST [main] INFO  s.d.s.w.s.ApiListingReferenceScanner - Scanning for api listing references
 2023-01-03 20:04:11.321 CST [main] INFO  com.xxx.XXApplication - Started XXApplication in 58.913 seconds (JVM running for 60.755)
复制代码

耗时排查

前提:我对 SpringBoot以及 Spring 的理解还局限在八股文,

思路1(笨方法) :所以对于排查的思路不说很聪明很有用,但至少很容易看懂。就是每一条日志看呗,如果明显的慢,在打印日志的时候肯定会停顿的,看一下接下来打印的日志是什么内容。排查的思路当然就是在上一句日志和打印出来的这句日志中找问题了。

思路2:SpringBoot 的启动过程,肯定会加载很多 Bean,并且这些 Bean 都会走一遍 Bean 的生命周期。只要在加载每个 Bean 的过程中,记录每个 Bean 的初始化耗时,就可以大致知道问题出在哪里。可以使用 BeanPostProcessor接口来监控 Bean 的注入耗时。BeanPostProcessor是 Spring 提供的 Bean 初始化前后的钩子函数,用于执行一切自定义逻辑。下面是借鉴掘金一位大佬文章中的代码。如果项目中的Bean数量不是特别多,我觉得可以换成 Stopwatch 来进行计时会更优雅一些!

 @Component
 public class TimeCostBeanPostProcessor implements BeanPostProcessor {
     private Map<String, Long> costMap = Maps.newConcurrentMap();
         
     @Override
     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         costMap.put(beanName, System.currentTimeMillis());
         return bean;
     }
     @Override
     public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
         if (costMap.containsKey(beanName)) {
             Long start = costMap.get(beanName);
             long cost  = System.currentTimeMillis() - start;
             if (cost > 0) {
                 costMap.put(beanName, cost);
                 System.out.println("bean: " + beanName + "\ttime: " + cost);
             }
         }
         return bean;
     }
 }
复制代码

思路3: 遇事不决加内存!

优化方案

对于思路1,就不多说了。其实没有那么好用。要不然我也不会去翻一开始说的那本书。

方案1:SpringBoot实现 Bean 的懒加载

SpringBoot实现这一功能的版本是:2.2。不符合项目要求

方案2:调整JVM参数实现启动优化

这个方案只是我个人认为是可行的,但其实没有什么用。主要是我理解的误区,我以为堆内存设置的足够大,减少 GC 频率,就可以提高启动速度。

有这种想法的原因是因为我在个人项目中使用过,因为这个项目刚起步,写的内容没那么多,随便设置几个JVM参数,堆内存设置大点,启动速度也就两三秒,不设置JVM参数,启动速度大概4~6秒。

而我在项目中使用,因为是本地,JVM参数并没有设置过,堆内存最小就是电脑内存的 1/64,最大就是内存的 1/4。结果堆内存设置大一点,其实启动速度没什么变化。

方案3(《Java应用提速》中提供):升级AspectJ版本

这种方案的前提是AspectJ的版本要低于1.9.0,并且如果应用中有使用到通过注解(RetentionPolicy需要是 RUNTIME)来做切面表达式

我看了书中的方案,就去翻了一下项目中关于AspectJ依赖的版本1.8.8,刚好在1.9.0下,并且翻了一下项目中所有做切面表达式的注解的生命周期都是RUNTIME的。

于是,我就试着修改 AspectJ版本,结果真的如书中所说,从 150秒 降到了 70多秒,提速了一半。

原理

在老版本的aspectJ 在判断一个bean的 method 上是否有 annotation的方法,发现它是去 jar 包里读取Class文件并解析类信息,耗时在类搜索和解析上。

unpackAnnotations()

annotaionFindler.getAnnotations()

而Method 类本身就有getAnnotation方法,不需要自己加载类信息和解析

AspectJ去 class 源文件中读取的原因是因为annotaion的生命周期如果不是RUNTIME的话,运行时是获取不到的

在1.9.0以后的版本中,已经解决该问题。

新版本在判断是否有注解的逻辑:与老版本的差异在于会判断 annotaion的生命周期是不是RUNTIME的,是的话,就直接从 Method中获取了,不是才会加载源文件解析

其实应该还是可以继续优化的,后面再找找有没有别的思路。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值