偶遇 JDK 1.8 還未修復的 SecureRandom.getInstance("SHA1PRNG") 之 bug
樓主今天興高采烈的在部署環境,下載 JDK,打包專案,上傳至伺服器。
配置 JDK ,打包上傳專案樓主就不在這裡重複了,讀者自行解決哈!
1. 啟動專案
java -jar xxxx.jar
令樓主沒有想到的是:程式卡主了,卡在了資料庫建立連線的位置。(檢視方法方式: jstack <pid> 即可)
2. 堆疊資訊
由於是專案剛一啟動,初始化資料庫連線池,並沒有太多的執行緒堆疊。這裡我貼一下我遇到的主要問題的堆疊資訊:
"restartedMain" #11 prio=5 os_prio=0 tid=0x00007f4430002800 nid=0x65b0 runnable [0x00007f447837a000] 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 <0x00000000d6eb8930> (a sun.security.provider.SecureRandom) at java.security.SecureRandom.nextBytes(SecureRandom.java:468) at oracle.security.o5logon.O5Logon.a(Unknown Source) at oracle.security.o5logon.O5Logon.<clinit>(Unknown Source) at oracle.jdbc.driver.T4CTTIoauthenticate.<init>(T4CTTIoauthenticate.java:566) at oracle.jdbc.driver.T4CConnection.logon(T4CConnection.java:370) at oracle.jdbc.driver.PhysicalConnection.<init>(PhysicalConnection.java:546) at oracle.jdbc.driver.T4CConnection.<init>(T4CConnection.java:236) at oracle.jdbc.driver.T4CDriverExtension.getConnection(T4CDriverExtension.java:32) at oracle.jdbc.driver.OracleDriver.connect(OracleDriver.java:521)
或許你會問我,為什麼你要定位到這個執行緒?主要是因為道友經過好幾次 jstack 操作,發現該執行緒一直都停留在該位置,這就足以說明這個執行緒是當前執行緒!
3. 分析堆疊資訊
從上面的堆疊資訊中,我們可以得知那些資訊?
- 當前執行緒名稱為 : restartedMain
- 當前執行緒狀態為: RUNNABLE
- 當前執行緒 ID: 0x00007f4430002800
- native 執行緒 ID: 0x65b0
- 當前執行位置 : at java.io.FileInputStream.readBytes(Native Method)
通過分析堆疊,想必讀取檔案不會有什麼問題,我們往執行緒堆疊的上層追蹤。目標點落在
at sun.security.provider.SeedGenerator$URLSeedGenerator.getSeedBytes(SeedGenerator.java:539)
很明顯,這裡是 JDK 原始碼的呼叫,接下來打卡 jdk 原始碼進行檢視!
4. 原始碼分析
我們通過堆疊資訊一步步跟進原始碼檢視,
從方法 nextBytes 上來看,他呼叫這個是為了:生成使用者指定的隨機位元組數。好吧,那就是它在生成的時候,需要讀取某個檔案來生成隨機數。
5. 寫個測試驗證想法
[ryan@ryanmacbook ~]$ vim HelloWorld.java
import java.security.SecureRandom;
class HelloWorld {
public static void main(String args[]) throws Exception {
byte[] bytes = new byte[32];
SecureRandom.getInstance("SHA1PRNG").nextBytes(bytes);
System.out.println("SourceRandom nextBytes : " + new String(bytes));
}
}
緊接著執行
javac HelloWorld.java
這個要是讀者你不會,那你就放棄java吧,java 界可能不適合你。
接下來執行
java HelloWorld
執行後,,的結果是
[ryan@ryanmacbook ~]$ java HelloWorld
^C
[ryan@ryanmacbook ~]$
卡住了,樓主強制 kill 了。
說明我們的問題點就是這裡。
接下來,解決辦法是啥?你要問樓主樓主也不知道,咋辦?上百度唄,百度都不會用的程式設計師不是好程式設計師:
6. 解決辦法
最後的解決辦法就是 ,在專案啟動的時候,修改 java.security.egd,通過如下方式,專業術語叫做 熵池策略 :
java -Djava.security.egd=file:/dev/urandom HelloWorld
問題的本質還是 Oracle 驅動類中呼叫了 SecureRandom.nextBytes() 。樓主還沒來得及看其他驅動程式是否也有這個問題,當然也不排除與作業系統有關係。待驗證其他作業系統後,再補充!
果斷成功!
道友查了下,說是 JVM 的 bug , 在收集噪點的時候沒有收集全導致的。如果你的也有遇到這樣的問題,時而可用,時而不可用,那就是噪點收集的問題。
[參考連結] http://ifeve.com/jvm-random-and-entropy-source/