接下來是對Java應用程序中特定類型的內存問題的實用介紹。 即–我們將分析導致java.lang.OutOfMemoryError:PermGen空間的錯誤 堆棧跟蹤中的癥狀。
首先,我們將介紹理解該主題所需的核心概念,并說明什么是對象,類,類加載器和JVM內存模型。 如果您熟悉基本概念,則可以直接跳到下一部分,在此我將描述所討論錯誤的兩種典型情況以及解決它的提示和建議。
對象,類和類加載器
好吧,我不會從最基本的內容開始。 我想如果您已經找到我們,那么您應該熟悉Java中的一切都是Object的概念。 并且所有對象均由其類指定。 因此,每個對象都有對java.lang.Class實例的引用,該實例描述了該對象的類的結構。
但是,當您在代碼中創建一個新對象時,實際上發生了什么呢? 例如,如果您寫的東西真的很復雜
人老板=新人()
Java虛擬機(JVM)需要了解要創建的對象的結構。 為此,JVM查找名為Person的類。 而且,如果在程序的特定執行期間第一次訪問Person類,則通常必須從JVM從相應的Person.class文件中加載它。 在驅動器上查找Person.class文件,將其加載到內存中并解析其結構的過程稱為類加載 。 確保正確的類加載過程是ClassLoader的責任。 ClassLoader是java.lang.ClassLoader類的實例,Java程序中的每個類都必須由某個ClassLoader進行加載。 結果,我們現在具有以下關系:
從下圖可以看到,每個類加載器均包含對其已加載的所有類的引用。 就本文而言,這些關系非常有趣。
記住此圖像,稍后我們將需要它。
永久世代
如今,幾乎每個JVM都使用一個單獨的內存區域(稱為Permanent Generation(簡稱PermGen ))來保存Java類的內部表示形式。 PermGen還用于存儲更多信息-如果您有興趣,請從這篇文章中查找詳細信息-但對于我們的文章,可以安全地假設僅類定義存儲在PermGen中。 在運行Java 1.6的兩臺計算機上,該區域的默認大小不是非常可觀的82MB。
正如我在之前的一篇文章中所解釋的那樣,Java中的內存泄漏是一種情況,其中某些對象不再被應用程序使用,但是垃圾收集器無法將其識別為未使用。 如果那些未使用的對象對堆使用的貢獻很大,以致于應用程序無法滿足下一個內存分配請求,則會導致OutOfMemoryError 。
java.lang.OutOfMemoryError:PermGen空間的根本原因是完全相同的:JVM需要加載新類的定義,但是PermGen中沒有足夠的空間來執行此操作–那里已經存儲了太多的類。 可能的原因是,您的應用程序或服務器使用了太多的類,而PermGen的當前大小無法容納它們。 另一個常見原因可能是內存泄漏。
永久泄漏
但是,仍然有可能在PermGen中泄漏某些東西嗎? 它保存著Java類的定義,它們不能成為未使用的,可以嗎? 實際上,他們可以。 如果將Java Web應用程序部署到應用程序服務器中,則在取消部署應用程序時,EAR / WAR中的所有這些類將變得毫無用處。 由于應用程序服務器仍處于活動狀態,因此JVM繼續運行,但是不再使用大量的類定義。 并且應該將它們從PermGen中刪除。 如果沒有,我們將在PermGen區域發生內存泄漏。
作為一個很好的例子,Tomcat開發人員已經建立了一個Wiki頁面,描述了在Apache Tomcat 6.0.24及更高版本中發現并修復的各種漏洞。
泄漏線程
類加載器泄漏的一種可能情況是長時間運行的線程。 當您的應用程序或您的應用程序使用的第三方庫(通常以我的經驗)啟動某個長時間運行的線程時,就會發生這種情況。 一個例子就是計時器線程,其任務是定期執行一些代碼。
如果該線程的預期壽命不確定,我們將直接陷入麻煩。 當應用程序的任何部分啟動線程時,必須確保它不會使應用程序壽命更長。 在典型情況下,開發人員要么不了解此責任,要么干脆忘了編寫清理代碼。
否則,如果在取消部署應用程序后某個線程繼續運行,則通常將保留對由其啟動的Web應用程序的類加載器的引用,稱為上下文類加載器 。 反過來,這意味著未部署的應用程序的所有類都繼續保留在內存中。 補救? 如果是您的應用程序啟動了新線程,則應在取消部署期間使用servlet上下文偵聽器將其關閉。 如果它是第三方庫,則應搜索其自己的特定關閉掛鉤。 或提交錯誤報告(如果沒有)。
驅動程序泄漏
數據庫驅動程序可能導致泄漏的另一種典型情況。 我們在Plumbr附帶的演示應用程序中遇到了此泄漏。 它是隨Spring MVC一起提供的經過稍微修改的Pet Clinic應用程序。 讓我們重點介紹將應用程序部署到服務器時發生的一些事情。
- 服務器創建一個新的java.lang.Classloader實例,并開始使用它加載應用程序的類。
- 由于PetClinic使用HSQL數據庫,因此它將加載相應的JDBC驅動程序org.hsqldb.jdbcDriver
- 此類是一種很好的JDBC驅動程序,根據JDBC規范的要求,在初始化期間將其自身注冊到java.sql.DriverManager 。 該注冊包括在DriverManager的靜態字段中存儲對org.hsqldb.jdbcDriver實例的引用。
現在,當從應用程序服務器取消部署應用程序時, java.sql.DriverManager仍將保留該引用,因為HSQLDB庫,Spring框架或應用程序中都沒有刪除該代碼的代碼! 如上文所述, jdbcDriver對象仍然持有對org.hsqldb.jdbcDriver類的引用,而該類又持有對用于加載應用程序的java.lang.Classloader實例的引用。 現在,該類加載器仍引用應用程序的所有類。 對于我們特定的演示應用程序,在應用程序啟動期間將加載近2000個類,在PermGen中大約占用10MB。 這意味著需要大約5到10個重新部署才能用默認大小填充PermGen,以達到java.lang.OutOfMemoryError:PermGen空間崩潰。
如何解決? 一種可能性是編寫一個Servlet上下文偵聽器,該偵聽器在應用程序關閉期間從DriverManager注銷HSQLDB驅動程序。 這很簡單。 但是請記住–您將必須使用驅動程序在每個應用程序中編寫相應的代碼。
使用我們的演示應用程序下載我們最新版本的Plumbr,并使用它來查找泄漏的發生方式,Plumbr如何發現泄漏以及如何解釋原因。
結論
您的應用程序可能遇到java.lang.OutOfMemoryError:PermGen space的原因很多。 導致它們大多數的根本原因是對由應用程序的類加載器加載的對象或類的引用,這些對象或類在此之后死亡。 或直接鏈接到類加載器本身。 對于大多數這些原因,您需要采取的補救措施非常相似。 首先,找出引用在哪里保存。 其次,向您的Web應用程序添加一個關閉掛鉤,以在取消部署應用程序時刪除引用。 您可以通過使用Servlet上下文偵聽器或通過第三方庫提供的API來實現。
找到那些泄漏的參考從未如此簡單。 我們自己花費了無數的時間試圖找出為什么某些應用程序每次重新部署都需要20MB的PermGen。 但是從1.1版開始,Plumbr將向您顯示泄漏的原因,并提示您如何修復它。 如果您想嘗試一下,請注冊并下載該工具 。 如果您運行的是Plumbr的舊版本,我們強烈建議您下載升級程序 。
參考: 什么是PermGen泄漏? 由我們的JCG合作伙伴 Nikita Salnikov Tarnovski在Plumbr Blog博客上獲得。
翻譯自: https://www.javacodegeeks.com/2012/12/what-is-a-permgen-leak.html