Hibernate 和 Spring Boot 記憶體洩漏問題報告

1. org.springframework.boot.loader 記憶體洩漏

發生原因: - 堆外記憶體使用:Spring Boot 在解壓 JAR 檔案時使用 Inflater 類來處理壓縮數據,這會消耗堆外記憶體。如果 Inflater 沒有正確釋放記憶體,可能會導致記憶體洩漏。 - LaunchedURLClassLoader:在某些情況下,LaunchedURLClassLoader 可能會持有對象,導致記憶體無法被垃圾回收器回收。 - 版本問題:某些版本的 Spring Boot 在處理 JAR 檔案時可能會引入記憶體問題,這些問題通常與 JAR 檔案的關閉和內部對象的持有有關。

解決方法: - 確保使用最新版本的 Spring Boot,因為新版本通常會修復已知的記憶體洩漏問題。 - 使用記憶體分析工具(如 jhatMemoryAnalyzer)來檢查記憶體使用情況,並找出可能的記憶體洩漏源。 - 在應用程式中明確釋放不再需要的資源,特別是那些使用堆外記憶體的對象。

2. org.hibernate.engine.query.spi.HQLQueryPlan 記憶體洩漏

發生原因: - 查詢計劃快取增長:Hibernate 快取 SQL 查詢計劃以提高性能,但如果應用程式生成許多唯一查詢(特別是帶有不同 IN 子句參數的查詢),快取可能會顯著增長,導致記憶體洩漏。 - BoundedConcurrentHashMapQueryPlanCache 使用 BoundedConcurrentHashMap 來存儲查詢計劃,如果管理不當,這可能會消耗大量記憶體。

解決方法: - 限制快取大小:在 application.properties 檔案中配置查詢計劃快取的最大大小:

spring.jpa.properties.hibernate.query.plan_cache_max_size=64
spring.jpa.properties.hibernate.query.plan_parameter_metadata_max_size=32
- IN 子句參數填充:啟用參數填充以減少生成的唯一查詢數量:
spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true
- 定期清理快取:實施機制定期清理或使舊的快取條目失效,以防止快取無限增長。

3. spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true 的作用

功用: - 查詢計劃重用:通過填充 IN 子句的參數,Hibernate 可以將不同參數數量的查詢視為相同的查詢,從而重用已經準備好的查詢計劃,減少查詢計劃的準備時間。 - 減少記憶體使用:減少了查詢計劃快取中的唯一查詢數量,從而降低記憶體使用量。

實際例子: 假設我們有一個查詢,用於查找多個 ID 對應的記錄:

String query = "SELECT p FROM Product p WHERE p.id IN (:ids)";
List<Long> ids1 = Arrays.asList(1L, 2L, 3L);
List<Long> ids2 = Arrays.asList(4L, 5L);

entityManager.createQuery(query)
             .setParameter("ids", ids1)
             .getResultList();

entityManager.createQuery(query)
             .setParameter("ids", ids2)
             .getResultList();
在啟用參數填充後,這些查詢會被填充到相同的參數數量,例如:
SELECT p FROM Product p WHERE p.id IN (?, ?, ?, ?, ?)
這樣,即使 ids1ids2 的參數數量不同,它們也會被視為相同的查詢,從而重用相同的查詢計劃。

4. spring.jpa.properties.hibernate.query.plan_cache_max_size=64 的作用

功用: - 提高性能:當相同的查詢再次執行時,Hibernate 可以直接從快取中獲取查詢計劃,而不需要重新編譯查詢,從而提高查詢性能。 - 記憶體管理:限制查詢計劃快取的大小可以防止記憶體過度使用,特別是在應用程式中有大量不同查詢的情況下。