Java的內存泄露

Java的內存泄露介紹

首先明確一下內存泄露的概念:內存泄露是指程序運行過程動態分配了內存,但是在程序結束的時候這塊內存沒有被釋放,從而導致這塊內存不可用,這就是內存

泄露,重啓計算機可以解決這個問題,但是有可能再次發生內存泄露,內存泄露與硬件沒有關係,它是軟件設計的缺陷所導致的。

Java發生內存泄露的原因很明確,就是長聲明週期對象持有短聲明週期對象的引用就很可能發生內存泄露。儘管短生命週期對象已經不再需要,但是因爲長生命

週期對象在持有它的引用而導致它不能被GC回收,這就是Java內存泄露發生的場景。

java內存泄露場景舉例:

當我們不斷的向集合類內添加元素,而沒有相應的刪除機制,導致內存一直被佔用,這樣也不一定就會造成內存泄露,當該集合類只是一個局部變

量的時候,當方法執行完畢退出的時候,自然會被GC所回收,但是怕的是該集合類是一個全局的屬性(比如類中的靜態變量),那麼會導致該集合類佔用的內存只

增不減,這樣就導致了內存的泄露。所以我們在使用全局性的集合類的時候要注意提供合適的刪除策略或者定期清理策略。

內存泄露可以分爲4類:

(1)常發性內存泄露:發生內存泄露的代碼會多次被執行到,每次執行的時候就會有一塊內存泄露

(2)偶發性內存泄露:發生內存泄露的代碼只在某些特定環境下才會發生的,常發性與偶發性是相對的,對於特定的環境下,偶發性也就是常發性的。所以,測

試方法和測試環境對檢測內存泄露有是很重要的。

(3)一次性內存泄露:發生內存泄露的代碼只會被執行一次,也就內存中總有那麼一塊內存是不可用的。

(4)隱式內存泄露    :程序執行過程中在不斷的分配內存,直到程序結束的時候纔會釋放所有的內存,嚴格的說,這裏並沒有發生內存泄露,因爲程序最終釋放

了所有申請的內存。但是對於一個服務器程序來說,往往會連續執行很多天甚至好幾個月的,這樣遲早會發生內存溢出的情況。所以,我們稱這類內存泄露爲隱式

內存泄露。

2、Java內存溢出的問題及解決辦法

JVM管理的內存大致分爲三種不同類型的內存區域:

Generation space(永久保存區域)、Heap space(堆區域)、JavaStacks(Java棧)。其中永久保存區域主要存放Class(類)和Meta的信息,Class第一次被Load

的時候被放入PermGenspace區域,Class需要存儲的內容主要包括方法和靜態屬性。堆區域用來存放Class的實例(即對象),對象需要存儲的內容主要是非靜態

屬性。每次用new創建一個對象實例後,對象實例存儲在堆區域中,這部分空間也被jvm的垃圾回收機制管理。而Java棧跟大多數編程語言包括彙編語言的棧功能相

似,主要基本類型變量以及方法的輸入輸出參數。Java程序的每個線程中都有一個獨立的堆棧。容易發生內存溢出問題的內存空間包括:

Permanent Generation space和Heap space。

第一種OutOfMemoryError PermGenspace

發生這種問題的原意是程序中使用了大量的jarclass,使java虛擬機裝載類的空間不夠,與PermanentGeneration space有關。解決這類問題有以下兩種辦法:

(1) 增加java虛擬機中的XX:PermSizeXX:MaxPermSize參數的大小,其中XX:PermSize是初始永久保存區域大小,XX:MaxPermSize是最大永久保存區域大

小。如針對tomcat6.0,在catalina.shcatalina.bat文件中一系列環境變量名說明結束處(大約在70行左右)增加一行:JAVA_OPTS=" -XX:PermSize=64M-

XX:MaxPermSize=128m"如果是windows服務器還可以在系統環境變量中設置。感覺用tomcat發佈sprint+struts+hibernate架構的程序時很容易發生這種內存溢出

錯誤。使用上述方法,我成功解決了部ssh項目的tomcat服務器經常宕機的問題。

(2) 清理應用程序中web-inf/lib下的jar,如果tomcat部署了多個應用,很多應用都使用了相同的jar,可以將共同的jar移到tomcat共同的lib下,減少類的重複加

載。這種方法是網上部分人推薦的,我沒試過,但感覺減少不了太大的空間,最靠譜的還是第一種方法。

第二種OutOfMemoryError  Javaheap space

發生這種問題的原因是java虛擬機創建的對象太多,在進行垃圾回收之間,虛擬機分配的到堆內存空間已經用滿了,與Heapspace有關。解決這類問題有兩種思

路:

(1)檢查程序,看是否有死循環或不必要地重複創建大量對象。找到原因後,修改程序和算法。

我以前寫一個使用K-Means文本聚類算法對幾萬條文本記錄(每條記錄的特徵向量大約10來個)進行文本聚類時,由於程序細節上有問題,就導致了Javaheap

 space的內存溢出問題,後來通過修改程序得到了解決

(2)增加Java虛擬機中Xms(初始堆大小)和Xmx(最大堆大小)參數的大小。如:setJAVA_OPTS= -Xms256m -Xmx1024m

3、Java的GC機制

一個優秀的程序員必須瞭解GC的原理、如何有變化GC的性能、如何與GC進行有限的交互,因爲一些程序對性能要求較高,例如嵌入式系統、實時系統等,只有

全面提升內存的管理效率,纔能有效提升程序的性能。

GC的基本原理:

Java的內存管理實際上就是對象的管理,包括對象的分配與釋放

對於程序員來說,分配對象使用new關鍵字,釋放對象就是將對象的所有引用賦值爲null,讓程序不能訪問到這個對象,我們稱之爲‘不可達’狀態,GC負責回收所

有不可達狀態的對象的內存空間。

對於GC來說,當程序員創建了對象,GC就開始監控這個對象的大小、地址、使用情況。通常GC採用有向圖的方式來管理堆(heap)中所有對象。通過這種方式

來確定哪些對象是可達的,哪些對象是不可達的,當GC確定了一些對象是不可達狀態的時候,GC就有責任將這些對象回收。但是,爲了保證GC能夠在不同平臺實

現的問題,Java規範對GC的很多行爲都沒有進行嚴格的規定。例如,對於採用什麼類型的回收算法、什麼時候進行回收等重要問題都沒有明確的規定。因此,不

同的JVM的實現者往往有不同的實現算法。

這也給Java程序員的開發帶來行多不確定性。本文研究了幾個與GC工作相關的問題,努力減少這種不確定性給Java程序帶來的負面影響。 

增量式GC( Incremental GC )

GC在JVM中通常是由一個或一組進程來實現的,它本身也和用戶程序一樣佔用heap空間,運行時也佔用CPU.當GC進程運行時,應用程序停止運行。

因此,當GC運行時間較長時,用戶能夠感到 Java程序的停頓,另外一方面,如果GC運行時間太短,則可能對象回收率太低,這意味着還有很多應該回收的對象沒

有被回收,仍然佔用大量內存。因此,在設計GC的時候,就必須在停頓時間和回收率之間進行權衡。一個好的GC實現允許用戶定義自己所需要的設置,例如有些內

存有限有設備,對內存的使用量非常敏感,希望GC能夠準確的回收內存,它並不在意程序速度的放慢。另外一些實時網絡遊戲,就不能夠允許程序有長時間的中

斷。增量式GC就是通過一定的回收算法,把一個長時間的中斷,劃分爲很多個小的中斷,通過這種方式減少GC對用戶程序的影響。雖然,增量式GC在整體性能上

可能不如普通GC的效率高,但是它能夠減少程序的最長停頓時間。Sun JDK提供的HotSpot JVM就能支持增量式GC.HotSpot JVM缺省GC方式爲不使用增量GC,爲

了啓動增量GC,我們必須在運行Java程序時增加-Xincgc的參數。

HotSpot JVM增量式GC的實現是採用Train GC算法。它的基本想法就是,將堆中的所有對象按照創建和使用情況進行分組(分層),將使用頻繁高和具有相關性

的對象放在一隊中,隨着程序的運行,不斷對組進行調整。當GC運行時,它總是先回收最老的(最近很少訪問的)的對象,如果整組都爲可回收對象,GC將整組回

收。這樣,每次GC運行只回收一定比例的不可達對象,保證程序的順暢運行。

詳解finalize函數

finalize是位於Object類的一個方法,該方法的訪問修飾符爲protected,由於所有類爲Object的子類,因此用戶類很容易訪問到這個方法。由於,finalize函

數沒有自動實現鏈式調用,我們必須手動的實現,因此finalize函數的最後一個語句通常是super.finalize()

通過這種方式,我們可以實現從下到上實現finalize的調用,即先釋放自己的資源,然後再釋放父類的資源。根據Java語言規範,JVM保證調用finalize函數之

前,這個對象是不可達的,但是JVM不保證這個函數一定會被調用。另外,規範還保證finalize函數最多運行一次。很多Java初學者會認爲這個方法類似與C++中

的析構函數,將很多對象、資源的釋放都放在這一函數裏面。其實,這不是一種很好的方式。原因有三:

其一,GC爲了能夠支持finalize函數,要對覆蓋這個函數的對象作很多附加的工作。

其二,在finalize運行完成之後,該對象可能變成可達的,GC還要再檢查一次該對象是否是可達的。因此,使用 finalize會降低GC的運行性能。

其三,由於GC調用finalize的時間是不確定的,因此通過這種方式釋放資源也是不確定的。通常,finalize用於一些不容易控制、並且非常重要資源的釋放,例

如一些I/O的操作,數據的連接。這些資源的釋放對整個應用程序是非常關鍵的。在這種情況下,程序員應該以通過程序本身管理(包括釋放)這些資源爲主,以

finalize函數釋放資源方式爲輔,形成一種雙保險的管理機制,而不應該僅僅依靠finalize來釋放資源。

發佈了11 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章