警告: The web application [ROOT] appears to have started a thread named [HouseKeeper] but has failed to stop it. This is very likely to create a memory leak.
Stack trace of thread:
java.lang.Thread.sleep(Native Method)
org.logicalcobwebs.proxool.HouseKeeperThread.run(HouseKeeperThread.java:46)
Proxool 關閉入口:ProxoolFacade.shutdown() → 關閉所有連接池 → HouseKeeperController.shutdown()。
protected static void shutdown() {
synchronized(LOCK) {
Iterator i = houseKeeperThreads.iterator();
while (i.hasNext()) {
HouseKeeperThread hkt = (HouseKeeperThread) i.next();
LOG.info("Stopping " + hkt.getName() + " thread");
hkt.cancel(); // 僅設置 stop = true
}
houseKeeperThreads.clear();
}
}
public class HouseKeeperThread extends Thread {
private boolean stop; // 關鍵缺陷:非 volatile!
public void run() {
while (!stop) {
HouseKeeper hk = HouseKeeperController.getHouseKeeperToRun();
while (hk != null && !stop) {
try { hk.sweep(); } catch (ProxoolException e) { LOG.error(...); }
hk = HouseKeeperController.getHouseKeeperToRun();
}
try {
Thread.sleep(5000); // 每 5 秒掃描一次
} catch (InterruptedException e) {
LOG.error("Interrupted", e);
}
}
}
protected void cancel() {
stop = true; // 普通寫入,無內存屏障
}
}
| 問題 | 後果 |
|---|---|
private boolean stop; 非 volatile | JIT 編譯器可能將 while (!stop) 優化為 if (!stop) while(true) (循環不變量外提),導致 cancel() 修改對運行線程**不可見**,線程永不退出。 |
| sleep(5000) 期間收到關閉請求 | 線程處於阻塞態,cancel() 設置 stop=true 後,線程醒來捕獲 InterruptedException,但 catch 塊僅記錄錯誤,**未檢查 stop 標誌**,循環繼續執行。 |
| 無 interrupt() 喚醒 | shutdown() 僅調用 cancel(),**未調用 thread.interrupt()** 打斷睡眠,線程需等待 5 秒自然醒來才能檢查 stop。 |
ServletContextListener.contextDestroyed() → 應用調用 ProxoolFacade.shutdown()。HouseKeeperController.shutdown() 遍歷調用 hkt.cancel() 設置 stop=true。interrupt(),線程**無法立即醒來**檢查 stop 標誌。stop 非 volatile,可能**永遠看不到 true**,導致真正的內存洩漏 (線程永存,持有類加載器引用,阻礙 Metaspace/Heap 回收)。[INFO] Shutting down 'autumn' pool immediately
[DEBUG] #0006 removed because of shutdown
[DEBUG] Connection #6 closed
...
[INFO] 'autumn' pool has been closed down ... in 6 milliseconds
[INFO] Stopping Prototyper thread
[INFO] Stopping HouseKeeper thread <-- 此處調用 cancel(),但線程未實際終止
連接池資源釋放無誤,**唯一隱患是 HouseKeeper 守護線程未終止**。
// 在 ServletContextListener.contextDestroyed() 中,調用 ProxoolFacade.shutdown() 後補充:
try {
Field f = HouseKeeperController.class.getDeclaredField("houseKeeperThreads");
f.setAccessible(true);
@SuppressWarnings("unchecked")
List threads = (List) f.get(null);
for (HouseKeeperThread hkt : threads) {
hkt.interrupt(); // 打斷 sleep,強制檢查 stop
hkt.join(2000); // 等待最多 2 秒
}
} catch (Exception e) {
log.error("強制停止 HouseKeeper 失敗", e);
}
interrupt() + 標誌位 + join() 才是標準停止模式,單純設置 boolean 無法打斷阻塞操作。setDaemon(true) 僅意味 JVM 退出時不等待它,**應用容器熱部署/關閉時必須顯式停止**,否則類加載器洩漏。此警告雖不直接導致業務故障,但揭示了框架底層並發編程缺陷與技術債風險。生產環境強烈建議**完成連接池遷移**,徹底消除隱患。