背景
線上一臺後端服務所在機器CPU
飆到300%多。。這個過程並不是一下子就完成的,而是過幾個小時就來一次,奇了怪了。
解決思路
- 保護現場;
- 查看日誌;
- 查看進程:
top -c
- 查看Java線程棧:
jstack -l 32508 > jstack.32508.log
- 查看Java堆內存:
jmap -dump:live,format=b,file=32508-1.bin 32508
- 工具分析:
MAT:https://www.eclipse.org/mat/downloads.php
- 解決問題
以下進行一一說明:
- 保護現場;
如果條件允許,要保留一個服務進行問題分析。
- 查看日誌;
肯定要看日誌的,有沒有報錯信息等。
- 查看進程:
top -c
查看對資源消耗嚴重的進程列表。
-
查看Java線程棧:
jstack -l 32508 > jstack.32508.log
可以看到解密相關線程阻塞。。 -
查看Java堆內存:
jmap -dump:live,format=b,file=32508-1.bin 32508
對線上環境,間隔時間,連續三次dump出堆內存信息。從這幾個文件大小也可以看出,隨着時間推移,內存佔用越來越大,請看下一步。
- 工具分析:
MAT:https://www.eclipse.org/mat/downloads.php
依次打開上一步導出的三個bin
文件:
可以發現,類javax.crypto.JceSecurity
的實例佔據的堆內存持續增加,最終導致OOM
,線程阻塞,CPU
飆升。。
- 解決問題
JceSecurity
類中有個Map,verificationResults
是一個靜態對象,無法被JVM回收。
問題代碼:
Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
Security.addProvider(provider);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
原來是每次用戶登錄,都有一個`BouncyCastleProvider`對象被放到`IdentityHashMap`中,而這個Map(static)又無法被回收。。
解決方法就是將`BouncyCastleProvider`作爲單例,而不是每次解密時都new一個新對象。
private static org.bouncycastle.jce.provider.BouncyCastleProvider bouncyCastleProvider = null;
public static synchronized org.bouncycastle.jce.provider.BouncyCastleProvider getInstance() {
if (bouncyCastleProvider == null) {
bouncyCastleProvider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
}
return bouncyCastleProvider;
}
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", getInstance());
本地環境,問題復現
進行本地測試,用到的工具:
- Java虛擬機可視化監控:
JConsole
,JavaVisualVM
(JDK自帶) - 壓力測試:
JMeter
(http://jmeter.apache.org/download_jmeter.cgi)
測試步驟如下:
1. 本地啓動後的服務;
2. `JavaVisualVM` 監聽服務的進程;
3. `JMeter`壓測解密相關接口;
內存從開始的幾百M到多線程壓測時的1G+,然後有一部分內存GC
無法回收,最終堆內存佔用保持在1G左右。。
將jmap -dump:live,format=b,file=11428.bin 11428
導出的bin
文件,加載至MAT
進行分析。
可以看到與線上環境的問題一樣,問題復現完成。問題不重要,這裏僅提供解決問題的一個思路。
If you have any questions or any bugs are found, please feel free to contact me.
Your comments and suggestions are welcome!