有些情况下tomcat启动非常慢,通过jstack查看当前堆栈
/opt/java/jdk1.8.0_121/bin/jstack 14970 > /home/ubuntu/j.log
关键内容
"main" #1 prio=5 os_prio=0 tid=0x00007fc69c00a000 nid=0x3a7b runnable [0x00007fc6a5db5000]
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 <0x00000006883eb138> (a sun.security.provider.SecureRandom)
at java.security.SecureRandom.nextBytes(SecureRandom.java:468)
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:266)
at org.apache.catalina.util.SessionIdGeneratorBase.getRandomBytes(SessionIdGeneratorBase.java:203)
at org.apache.catalina.util.StandardSessionIdGenerator.generateSessionId(StandardSessionIdGenerator.java:34)
at org.apache.catalina.util.SessionIdGeneratorBase.generateSessionId(SessionIdGeneratorBase.java:195)
at org.apache.catalina.util.SessionIdGeneratorBase.startInternal(SessionIdGeneratorBase.java:285)
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
- locked <0x00000006883ead88> (a org.apache.catalina.util.StandardSessionIdGenerator)
分下SeedGenerator源码,URLSeedGenerator为SeedGenerator的内部类
1 static class URLSeedGenerator extends SeedGenerator {
2 private String deviceName;
3 private InputStream seedStream;
4
5 URLSeedGenerator(String var1) throws IOException {
6 if(var1 == null) {
7 throw new IOException("No random source specified");
8 } else {
9 this.deviceName = var1;
10 this.init();
11 }
12 }
13
14 private void init() throws IOException {
15 final URL var1 = new URL(this.deviceName);
16
17 try {
18 this.seedStream = (InputStream)AccessController.doPrivileged(new PrivilegedExceptionAction() {
19 public InputStream run() throws IOException {
20 if(var1.getProtocol().equalsIgnoreCase("file")) {
21 File var1x = SunEntries.getDeviceFile(var1);
22 return new FileInputStream(var1x);
23 } else {
24 return var1.openStream();
25 }
26 }
27 });
28 } catch (Exception var3) {
29 throw new IOException("Failed to open " + this.deviceName, var3.getCause());
30 }
31 }
32
33 void getSeedBytes(byte[] var1) {
34 int var2 = var1.length;
35 int var3 = 0;
36
37 try {
38 while(var3 < var2) {
39 int var4 = this.seedStream.read(var1, var3, var2 - var3);
40 if(var4 < 0) {
41 throw new InternalError("URLSeedGenerator " + this.deviceName + " reached end of file");
42 }
43
44 var3 += var4;
45 }
46
47 } catch (IOException var5) {
48 throw new InternalError("URLSeedGenerator " + this.deviceName + " generated exception: " + var5.getMessage(), var5);
49 }
50 }
51 }
getSeedBytes函数使用seedStream读取数据,seedStream在init()函数中初始化,实际上代表的是deviceName文件输入流,具体deviceName的路径初始化由URLSeedGenerator的构造函数完成。
SeedGenerator的静态构造器中初始化了SeedGenerator.URLSeedGenerator
1 static {
2 String var0 = SunEntries.getSeedSource();
3 if(!var0.equals("file:/dev/random") && !var0.equals("file:/dev/urandom")) {
4 if(var0.length() != 0) {
5 try {
6 instance = new SeedGenerator.URLSeedGenerator(var0);
7 if(debug != null) {
8 debug.println("Using URL seed generator reading from " + var0);
9 }
10 } catch (IOException var2) {
11 if(debug != null) {
12 debug.println("Failed to create seed generator with " + var0 + ": " + var2.toString());
13 }
14 }
15 }
16 } else {
17 try {
18 instance = new NativeSeedGenerator(var0);
19 if(debug != null) {
20 debug.println("Using operating system seed generator" + var0);
21 }
22 } catch (IOException var3) {
23 if(debug != null) {
24 debug.println("Failed to use operating system seed generator: " + var3.toString());
25 }
26 }
27 }
28
29 if(instance == null) {
30 if(debug != null) {
31 debug.println("Using default threaded seed generator");
32 }
33
34 instance = new SeedGenerator.ThreadedSeedGenerator();
35 }
36
37 }
如果 SunEntries.getSeedSource() 返回内容不为空,那么文件就是"file:/dev/random" 或者 "file:/dev/urandom",否则是NativeSeedGenerator,从jstack的打印来看流程是走到 SeedGenerator.URLSeedGenerator中。
1 final class SunEntries {
2 private static final String PROP_EGD = "java.security.egd";
3 private static final String PROP_RNDSOURCE = "securerandom.source";
4 static final String URL_DEV_RANDOM = "file:/dev/random";
5 static final String URL_DEV_URANDOM = "file:/dev/urandom";
6 private static final String seedSource = (String)AccessController.doPrivileged(new PrivilegedAction() {
7 public String run() {
8 String var1 = System.getProperty("java.security.egd", "");
9 if(var1.length() != 0) {
10 return var1;
11 } else {
12 var1 = Security.getProperty("securerandom.source");
13 return var1 == null?"":var1;
14 }
15 }
16 });
17
18 private SunEntries() {
19 }
20
21 static void putEntries(Map<Object, Object> var0) {
22 boolean var1 = NativePRNG.isAvailable();
23 boolean var2 = seedSource.equals("file:/dev/urandom") || seedSource.equals("file:/dev/random");
24 if(var1 && var2) {
25 var0.put("SecureRandom.NativePRNG", "sun.security.provider.NativePRNG");
26 }
27
28 var0.put("SecureRandom.SHA1PRNG", "sun.security.provider.SecureRandom");
29 if(var1 && !var2) {
30 var0.put("SecureRandom.NativePRNG", "sun.security.provider.NativePRNG");
31 }
32
33 if(Blocking.isAvailable()) {
34 var0.put("SecureRandom.NativePRNGBlocking", "sun.security.provider.NativePRNG$Blocking");
35 }
36
37 if(NonBlocking.isAvailable()) {
38 var0.put("SecureRandom.NativePRNGNonBlocking", "sun.security.provider.NativePRNG$NonBlocking");
39 }
40
41 var0.put("Signature.SHA1withDSA", "sun.security.provider.DSA$SHA1withDSA");
42 ....
43 }
44
45 static String getSeedSource() {
46 return seedSource;
47 }
48
49 static File getDeviceFile(URL var0) throws IOException {
50 try {
51 URI var1 = var0.toURI();
52 if(var1.isOpaque()) {
53 URI var2 = (new File(System.getProperty("user.dir"))).toURI();
54 String var3 = var2.toString() + var1.toString().substring(5);
55 return new File(URI.create(var3));
56 } else {
57 return new File(var1);
58 }
59 } catch (URISyntaxException var4) {
60 return new File(var0.getPath());
61 }
62 }
63 }
seedSource的初始化参考PrivilegedAction的run函数,如果系统设置java.security.egd属性,那么从此处取seedSource表示的文件,否则调用Security.getProperty("securerandom.source")。
Security.getProperty读取jre下面的配置文件/opt/java/jdk1.8.0_121/jre/lib/security/java.security,java.security配置文件配置了/dev/random,参考下图。
可能由于系统interrupt不足,导致在jdk在使用/dev/random时卡死。
注:想让System.getProperty("java.security.egd", "")不空,可以在Java启动参数中设置-Djava.security.egd=xxxx的方式
解决方法:
1、即在java程序启动参数中添加:-Djava.security.egd=file:/dev/./urandom,使用/dev/urandom生成随机数。
注:Java5以后file:/dev/./urandom,注意两个/之间的.
2、或者直接修改jre/lib/security/java.security配置文件,指向file:/dev/urandom
/dev/random和/dev/urandom的区别可以参考 https://lwn.net/Articles/184925/
The /dev/randomdevice was specifically designed to block when the entropy pool had insufficient entropy to satisfy the request. The /dev/urandom device is provided as an alternative that generates very good random numbers and does not block (and is therefore not vulnerable to a denial of service). For any but the most sensitive applications (key generation being an obvious choice), /dev/urandom is the recommended source for random numbers.
推荐:
微信扫码小程序使用更方便