首页 > 代码库 > tomcat启动非常慢原因深入分析
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生成随机数。
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.
tomcat启动非常慢原因深入分析