tomcat启动非常慢原因深入分析

Posted Jeffrey

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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.

 

以上是关于tomcat启动非常慢原因深入分析的主要内容,如果未能解决你的问题,请参考以下文章

docker+tomcat 启动时非常慢原因之JRE /dev/random阻塞

tomcat启动越来越慢了?

tomcat8启动慢原因及解决办法

RHEL7下Tomcat启动慢的原因及解决方案

Tomcat 启动慢 如何优化?

Eclipse启动项目超级的慢,这是为啥