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

来源:互联网 发布:淘宝大文 编辑:程序博客网 时间:2024/06/04 18:11
有些情况下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.

 

原创粉丝点击