背景
项目使用spring boot框架,在开发环境和测试环境启动没有问题。测试人员每次jenkins发布线上环境时,老是会阻塞在某个地方等个一分钟左右才会继续打印启动日志。
top 看了一下各项指标正常
线程阻塞状态下多次jstack线程日志,发现如下日志信息比较可疑
"localhost-startStop-1" #37 daemon prio=5 os_prio=0 tid=0x00007fe65c007000 nid=0x4017 runnable [0x00007fe6889d3000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(FileInputStream.java:255)
at sun.security.provider.SeedGenerator$URLSeedGenerator.getSeedBytes(SeedGenerator.java:539)
at sun.security.provider.SeedGenerator.generateSeed(SeedGenerator.java:144)
at sun.security.provider.SecureRandom$SeederHolder.<clinit>(SecureRandom.java:203)
at sun.security.provider.SecureRandom.engineNextBytes(SecureRandom.java:221)
- locked <0x00000007b826ae70> (a sun.security.provider.SecureRandom)
at java.security.SecureRandom.nextBytes(SecureRandom.java:468)
- locked <0x00000007b826b190> (a java.security.SecureRandom)
at java.security.SecureRandom.next(SecureRandom.java:491)
at java.util.Random.nextInt(Random.java:329)
at org.apache.catalina.util.SessionIdGeneratorBase.createSecureRandom(SessionIdGeneratorBase.java:237)
at org.apache.catalina.util.SessionIdGeneratorBase.getRandomBytes(SessionIdGeneratorBase.java:174)
at org.apache.catalina.util.StandardSessionIdGenerator.generateSessionId(StandardSessionIdGenerator.java:34)
at org.apache.catalina.util.SessionIdGeneratorBase.generateSessionId(SessionIdGeneratorBase.java:167)
at org.apache.catalina.util.SessionIdGeneratorBase.startInternal(SessionIdGeneratorBase.java:256)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
- locked <0x00000007b826aaa8> (a org.apache.catalina.util.StandardSessionIdGenerator)
at org.apache.catalina.session.ManagerBase.startInternal(ManagerBase.java:620)
at org.apache.catalina.session.StandardManager.startInternal(StandardManager.java:456)
- locked <0x00000007b1af0e40> (a org.apache.catalina.session.StandardManager)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
- locked <0x00000007b1af0e40> (a org.apache.catalina.session.StandardManager)
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5272)
- locked <0x00000007b2213788> (a org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedContext)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
- locked <0x00000007b2213788> (a org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedContext)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1408)
at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1398)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
初步定位是tomcat的session id的生成主要通过java.security.SecureRandom生成随机数来实现。URLSeedGenerator.getSeedBytes()
方法发生了阻塞,查看了一下源码:
@Override
void getSeedBytes(byte[] result) {
int len = result.length;
int read = 0;
try {
while (read < len) {
int count = seedStream.read(result, read, len - read);
// /dev/random blocks - should never have EOF
if (count < 0) {
throw new InternalError(
"URLSeedGenerator " + deviceName +
" reached end of file");
}
read += count;
}
} catch (IOException ioe) {
throw new InternalError("URLSeedGenerator " + deviceName +
" generated exception: " + ioe.getMessage(), ioe);
}
}
网上查询了一下/dev/random用于生成真随机数,在熵用完情况下会一直阻塞等到熵池中有足够的熵。
JDK默认配置如下:
# $JAVA_HOME/jre/lib/security/java.security
securerandom.source=file:/dev/urandom
解决方案
因为不是支付类系统对随机数的要求不高,再加之系统中基本没有使用到随机函数,可以改为使用无阻塞的熵池,具体修改如下 :
// 启动脚本中添加如下JVM参数配置, 注意【.】的用法
-Djava.security.egd=file:/dev/./urandom