Java的一個重要優勢就是經過垃圾收集器(Garbage Collection,GC)自動管理內存的回收,程序員不須要經過調用函數來釋放內存。所以,不少程序員認爲Java不存在內存泄漏問題,或者認爲即便有內存泄漏也不是程序的責任,而是GC或JVM的問題。其實,這種想法是不正確的,由於Java也存在內存泄露,但它的表現與C++不一樣。html
隨着愈來愈多的服務器程序採用Java技術,例如JSP,Servlet, EJB等,服務器程序每每長期運行。另外,在不少嵌入式系統中,內存的總量很是有限。內存泄露問題也就變得十分關鍵,即便每次運行少許泄漏,長期運行以後,系統也是面臨崩潰的危險。程序員
爲了判斷Java中是否有內存泄露,咱們首先必須瞭解Java是如何管理內存的。Java的內存管理就是對象的分配和釋放問題。在Java中,程序員須要經過關鍵字new爲每一個對象申請內存空間 (基本類型除外),全部的對象都在堆 (Heap)中分配空間。另外,對象的釋放是由GC決定和執行的。在Java中,內存的分配是由程序完成的,而內存的釋放是有GC完成的,這種收支兩條線的方法確實簡化了程序員的工做。但同時,它也加劇了JVM的工做。這也是Java程序運行速度較慢的緣由之一。由於,GC爲了可以正確釋放對象,GC必須監控每個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都須要進行監控。算法
監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象再也不被引用。編程
爲了更好理解GC的工做原理,咱們能夠將對象考慮爲有向圖的頂點,將引用關係考慮爲圖的有向邊,有向邊從引用者指向被引對象。另外,每一個線程對象能夠做爲一個圖的起始頂點,例如大多程序從main進程開始執行,那麼該圖就是以main進程頂點開始的一棵根樹。在這個有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們認爲這個(這些)對象再也不被引用,能夠被GC回收。服務器
如下,咱們舉一個例子說明如何用有向圖表示內存管理。對於程序的每個時刻,咱們都有一個有向圖表示JVM的內存分配狀況。如下右圖,就是左邊程序運行到第6行的示意圖。網絡
下面,咱們就能夠描述什麼是內存泄漏。在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色,首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連;其次,這些對象是無用的,即程序之後不會再使用這些對象。若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。函數
在C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,而後卻不可達,因爲C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,所以程序員不須要考慮這部分的內存泄露。工具
經過分析,咱們得知,對於C++,程序員須要本身管理邊和頂點,而對於Java程序員只須要管理邊就能夠了(不須要管理頂點的釋放)。經過這種方式,Java提升了編程的效率。性能
所以,經過以上分析,咱們知道在Java中也有內存泄漏,但範圍比C++要小一些。由於Java從語言上保證,任何對象都是可達的,全部的不可達對象都由GC管理。ui
對於程序員來講,GC基本是透明的,不可見的。雖然,咱們只有幾個函數能夠訪問GC,例如運行GC的函數System.gc(),可是根據Java語言規範定義, 該函數不保證JVM的垃圾收集器必定會執行。由於,不一樣的JVM實現者可能使用不一樣的算法管理GC。一般,GC的線程的優先級別較低。JVM調用GC的策略也有不少種,有的是內存使用到達必定程度時,GC纔開始工做,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但一般來講,咱們不須要關心這些。除非在一些特定的場合,GC的執行影響應用程序的性能,例如對於基於Web的實時系統,如網絡遊戲等,用戶不但願GC忽然中斷應用程序執行而進行垃圾回收,那麼咱們須要調整GC的參數,讓GC可以經過平緩的方式釋放內存,例如將垃圾回收分解爲一系列的小步驟執行,Sun提供的HotSpot JVM就支持這一特性。
下面給出了一個簡單的內存泄露的例子。在這個例子中,咱們循環申請Object對象,並將所申請的對象放入一個Vector中,若是咱們僅僅釋放引用自己,那麼Vector仍然引用該對象,因此這個對象對GC來講是不可回收的。所以,若是對象加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector對象設置爲null。
Vector v=new Vector(10); for (int i=1;i<100; i++) { Object o=new Object(); v.add(o); o=null; }//此時,全部的Object對象都沒有被釋放,由於變量v引用這些對象。
最後一個重要的問題,就是如何檢測Java的內存泄漏。目前,咱們一般使用一些工具來檢查Java程序的內存泄漏問題。市場上已有幾種專業檢查Java內存泄漏的工具,它們的基本工做原理大同小異,都是經過監測Java程序運行時,全部對象的申請、釋放等動做,將內存管理的全部信息進行統計、分析、可視化。開發人員將根據這些信息判斷程序是否有內存泄漏問題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,咱們將簡單介紹Optimizeit的基本功能和工做原理。
Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四類應用,而且能夠支持大多數類型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。而且,該軟件是由Java編寫,所以它支持多種操做系統。Optimizeit系列還包括Thread Debugger和Code Coverage兩個工具,分別用於監測運行時的線程狀態和代碼覆蓋面。
當設置好全部的參數了,咱們就能夠在OptimizeIt環境下運行被測程序,在程序運行過程當中,Optimizeit能夠監視內存的使用曲線(以下圖),包括JVM申請的堆(heap)的大小,和實際使用的內存大小。另外,在運行過程當中,咱們能夠隨時暫停程序的運行,甚至強行調用GC,讓GC進行內存回收。經過內存使用曲線,咱們能夠總體瞭解程序使用內存的狀況。這種監測對於長期運行的應用程序很是有必要,也很容易發現內存泄露。
在運行過程當中,咱們還能夠從不一樣視角觀查內存的使用狀況,Optimizeit提供了四種方式:
在運行過程當中,咱們能夠隨時觀察內存的使用狀況,經過這種方式,咱們能夠很快找到那些長期不被釋放,而且再也不使用的對象。咱們經過檢查這些對象的生存週期,確認其是否爲內存泄露。在實踐當中,尋找內存泄露是一件很是麻煩的事情,它須要程序員對整個程序的代碼比較清楚,而且須要豐富的調試經驗,可是這個過程對於不少關鍵的Java程序都是十分重要的。
綜上所述,Java也存在內存泄露問題,其緣由主要是一些對象雖然再也不被使用,但它們仍然被引用。爲了解決這些問題,咱們能夠經過軟件工具來檢查內存泄露,檢查的主要原理就是暴露出全部堆中的對象,讓程序員尋找那些無用但仍被引用的對象。