You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
2022-12-19 18:52:45.440 - 6557 [main] INFO com.Application:658 : The following profiles are active: dev
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils$1 (file:/H:/maven/org/springframework/spring-core/5.0.12.RELEASE/spring-core-5.0.12.RELEASE.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2022-12-19 18:53:18.903 - 40020 [main] INFO org.apache.coyote.http11.Http11NioProtocol:180 : Initializing ProtocolHandler ["http-nio-0.0.0.0-3000"]
这部分警告日志的前后间隔了大约 30 秒,说明 Spring 初始化有点慢,但看不到具体的细节,需要在 log4j2 配置中放开一下 Spring 的 debug 日志:
2022-12-19 19:15:29.185 - 7538 [main] INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:326 : Bean 'rocketmq-org.apache.rocketmq.spring.autoconfigure.RocketMQProperties' of type [org.apache.rocketmq.spring.autoconfigure.RocketMQProperties] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-12-19 19:15:39.070 - 17423 [main] INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:326 : Bean 'defaultMQProducer' of type [org.apache.rocketmq.client.producer.DefaultMQProducer] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-12-19 19:15:39.120 - 17473 [main] INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:326 : Bean 'jacksonObjectMapper' of type [com.fasterxml.jackson.databind.ObjectMapper] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2022-12-19 19:16:03.671 - 42024 [main] INFO org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker:326 : Bean 'rocketMQTemplate' of type [org.apache.rocketmq.spring.core.RocketMQTemplate] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
WARN 6244 --- [nio-8080-exec-1] o.a.c.util.SessionIdGeneratorBase : Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [6,337] milliseconds.
背景
打开一个几年前的老项目,发现启动巨慢,同样的代码没动过,当年在渣渣 i5 上运行也只要十几秒,现在升了配置反而要好几分钟。
问题定位
首先注意到这句报错,意思是 RocketMQ 没连上 ,按理说应该没影响,不过以防万一还是本地启动一下 RocketMQ :
start mqnamesrv && mqbroker -c ../conf/broker.conf
重启项目发现耗时还是差不多( 相对于总时长来说几秒的误差可以忽略不计 )。
再注意到下面的日志, RocketMQListener 注册成功之前等待了大约 10 秒的时间。
为了测试方便,之前只保留了一个 RocketMQListener ,其他的都注释掉了,现在放开注释后发现,每个 RocketMQListener 注册成功之前都会等待大约 10 秒。说明这部分的代码是有点问题的,但还不是启动慢的主要原因,继续看日志( 把 RocketMQListener 都先注释掉 ):
这部分警告日志的前后间隔了大约 30 秒,说明 Spring 初始化有点慢,但看不到具体的细节,需要在 log4j2 配置中放开一下 Spring 的 debug 日志:
可以看到每个 Bean 实例化都挺快的,唯独下面两个部分卡了一会儿:
这里耗时大约 30 秒,和前面可以对上。
由于项目目前用不上 RocketMQ ,可以把 RocketMQAutoConfiguration 配置先屏蔽掉看下:
重启项目只需要 20 多秒了,虽然还是慢但至少能接受,可以确定是 RocketMQ 的问题了。而且所有依赖的中间件都是本地部署的,基本可以忽略网络问题了( 后面打脸 ),实在想不通为什么会慢。
解决方法
拿着
rocketMQTemplate 启动慢
的关键词去搜问题简直是大海捞针,不过运气好找到了一篇文章《 RocketMQ 很慢?引出了一个未解之谜 》, 里面指出了NetworkInterface.getNetworkInterfaces
这个 JDK 方法的耗时问题 ,本地测试了一下果然如此:真凶果然是它,一次调用耗时将近 6 秒,不加限制多调用几次就能爆炸了。可以看一下这个方法的返回结果是什么:
光看名字就能联想到 控制面板\网络和 Internet\网络连接 中的 网络适配器 ,基本能对上:
之所以有这么多的设备是因为之前安装 Genymotion 模拟器的时候 VirtualBox 给安装上的,因为没遇到过其他问题也就没在意,没想到却导致了
getNetworkInterfaces
调用变慢的问题。而getNetworkInterfaces
方法底层调用的是getAll
这个 native 方法,所以代码层面基本没得改了,目前 唯一 的 解决方法 就是 把没用的网络适配器都禁用掉 ,最后耗时只要 600 毫秒了( 不同的网卡耗时程度不同 ),这种程度基本能接受了。回到项目还原代码验证一下,启动时间果然恢复到正常的 8 秒了。
问题复盘
这个问题得到解决得亏有人用 RocketMQ 的时候遇到类似的问题,不然就只能不了了之了。不过和别人说
getNetworkInterfaces
方法调用很慢大概率会被人当傻子,比如有人在 RocketMQ 仓库提过 类似问题 直接被无视了。继续查资料看有没有方法可以定位耗时的代码块,在《 SpringBoot 服务启动慢排查思路 》一文中看到了一张 JProfiler 的方法调用树截图,里面清楚标红了耗时比较长的方法,但是文章中没有具体的操作步骤,只能自己研究下。
操作步骤:
下载安装 JProfiler
在 IDEA 中用 JProfiler 图标启动项目
进 JProfiler 的 bin 目录启动主程序 jprofiler.exe
Attach > 选择 背景色标绿 的项目进程 > 开始
配置项一路确定就好了。
许可证密钥 错误会导致 启动报错 的,需要选择 评估 ( 试用 10 天 )。
清除已填写的注册码:
CPU 视图 > 调用树 > 开始记录
这一步手速要快一点,不然可能会错过一些重要的调用过程。当然也可以在 会话启动 的配置窗口中设置 初始化记录分析 以自动开始记录。
等待项目启动完成
调用树结果是有了,但坑爹的是展开之后报红的线索就断了,基本是 1% 以内的正常方法调用,完全看不到里层的慢方法。点击工具栏中的 分析 按钮,将结果转为火焰图看下:
可以看到两侧缺失了一大片的内容,说明有一些方法被隐藏了没记录在内,这时回到调用树,注意到每个方法前面的图标都不太一样,正好工具栏有一个 显示图例 的按钮,点击查看内容:
这说明部分方法被过滤了,最后在 会话设置 > 调用树过滤器 > 定义过滤器 中发现默认配置把一堆第三方库都过滤了:
这里只需要添加被分析的包( 注意顺序,具体配置规则见 官方文档 )即可:
重启项目重新开始分析就能得到想要的结果了:
查看火焰图效果更直观,可以看到大部分时间都花费在 getNetworkInterfaces 上了( 8 次调用共耗时 37 秒 ),也证实了前面问题定位没有错。
其他相关问题
解决完这个问题忽然想到前段时间碰到的另一个问题:用到 JSP 的项目从 JDK1.8 切换到 JDK11 后,第一次访问页面很慢,并有警告日志:
SecureRandom 的耗时问题 以前在 Docker 容器 中运行 Tomcat 时遇到过,但和这次显然不是一回事,定位到打日志的代码:
发现耗时的地方在
SecureRandom.nextInt()
,只是肉眼去看代码不好去分析更底层的调用栈,还好刚学会了 JProfiler ,分析结果一目了然,这个问题同样也是getNetworkInterfaces
在捣鬼。至于为什么 JDK1.8 没问题,由于调用栈发生了变化一时也查不出原因,我猜测可能是其他地方( 可能无法被分析器拦截到 )提前触发了
SecureRandom.SeederHolder
的 static 代码初始化。比如 debug 模式 运行项目时打断点发现sun.management.Agent.startAgent()
中就间接调用了SecureRandom
。转载请注明出处: https://github.com/anyesu/blog/issues/44
The text was updated successfully, but these errors were encountered: