项目场景:
某个项目中,sonar扫描到使用Random随机函数不安全, 推荐使用SecureRandom替换,于是就换成了使用SecureRandom.getInstanceStrong()获取SecureRandom并调用nextInt()。
悲剧开始了...
问题描述
在生产环境(linux)产生较长时间的阻塞,造成了nginx返回大量499,一度以为是tomcat不接收消息或是nginx转发问题,但开发环境(windows10)并未重现。
原因分析:
通过jstack发现,有599个线程的状态为waiting for monitor entry,在等待一个对象,而拥有这个对象的线程又被阻塞。
可以看到该线程阻塞在了java.io.FileInputStream.readBytes(Native Method)这个读取文件的IO处。对NativePRNG的部分关键源码进行分析:
// name of the pure random file (also used for setSeed())
private static final String NAME_RANDOM = "/dev/random";
// name of the pseudo random file
private static final String NAME_URANDOM = "/dev/urandom";
private static RandomIO initIO(final Variant v) {
return AccessController.doPrivileged(
new PrivilegedAction<RandomIO>() {
@Override
public RandomIO run() {
File seedFile;
File nextFile;
switch(v) {
//...忽略中间代码
case BLOCKING: // blocking状态下从/dev/random文件中读取
seedFile = new File(NAME_RANDOM);
nextFile = new File(NAME_RANDOM);
break;
case NONBLOCKING: // unblocking状态下从/dev/urandom文件中读取数据
seedFile = new File(NAME_URANDOM);
nextFile = new File(NAME_URANDOM);
break;
//...忽略中间代码
try {
return new RandomIO(seedFile, nextFile);
} catch (Exception e) {
return null;
}
}
});
}
// constructor, called only once from initIO()
private RandomIO(File seedFile, File nextFile) throws IOException {
this.seedFile = seedFile;
seedIn = new FileInputStream(seedFile);
nextIn = new FileInputStream(nextFile);
nextBuffer = new byte[BUFFER_SIZE];
}
private void ensureBufferValid() throws IOException {
long time = System.currentTimeMillis();
if ((buffered > 0) && (time - lastRead < MAX_BUFFER_TIME)) {
return;
}
lastRead = time;
readFully(nextIn, nextBuffer);
buffered = nextBuffer.length;
}
从源代码分析,发现导致阻塞的原因是因为从/dev/random中读取随机数导致。
解决方案:
1. 可以通过修改环境配置解决:
打开$JAVA_PATH/jre/lib/security/java.security这个文件,找到下面的内容:
securerandom.source=file:/dev/random
替换成:
securerandom.source=file:/dev/./urandom
2. 修改代码:
不推荐使用SecureRandom.getInstanceStrong()方式获取SecureRandom(除非对随机要求很高)。
推荐使用new SecureRandom()获取SecureRandom,linux下从/dev/urandom读取。虽然是伪随机,但大部分场景下都满足。
参考:
SecureRandom.getInstanceStrong()引发的线程阻塞问题分析_磨唧的博客-CSDN博客_getinstancestrong