tomcat启动非常慢原因深入分析
有些情况下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.