理解JVM的垃圾回收機制(簡稱GC)有什麼好處呢?做爲一名軟件開發者,知足本身的好奇心將是一個很好的理由,不過更重要的是,理解GC工做機制能夠幫助你寫出更好的Java程序。java
在學習GC前,你應該知道一個技術名詞:「stop-the-world」 ,不管你選擇哪一種GC算法,「stop-the-world」都會發生。「stop-the-world」意味着JVM中止應用程序,而去進行垃圾回收。當「stop-the-world」發生時,除了進行垃圾回收的線程,其餘全部線程都將中止運行。被中斷的任務將在GC任務完成後恢復執行。GC調優每每意味着減小「stop-the-world」的時間。git
在HotSpot虛擬機中,將內存分爲 年輕代(young generation)、老年代(old generation) 和 永久代(permanent generation)github
咱們看一下這幅圖:算法
年輕代: 新建立的對象都存放在這裏。由於大多數對象很快變得不可達,因此大多數對象在年輕代中建立,而後消失。當對象從這塊內存區域消失時,咱們說發生了一次「minor GC」。服務器
老年代: 沒有變得不可達,存活下來的年輕代對象被複制到這裏。這塊內存區域通常大於年輕代。由於它更大的規模,GC發生的次數比在年輕代的少。對象從老年代消失時,咱們說「major GC」(或「full GC」)發生了。框架
永久代: 永久代(permanent generation) 也稱爲「方法區(method area)」,它存儲class對象和字符串常量。因此這塊內存區域絕對不是永久的存放從老年代存活下來的對象的。發生在這裏的垃圾回收也被稱爲major GC。函數
垃圾收集算法以下:性能
該算法的核心是將可用內存按容量劃分爲大小相等的兩塊,每次只用其中一塊,當這一塊的內存用完,就將還存活的對象複製到另一塊上面,而後把已使用過的內存空間一次清理掉。這使得每次只對其中一塊內存進行回收,分配也就不用考慮內存碎片等複雜狀況,實現簡單且運行高效。以下圖:學習
該算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象(可達性分析),在標記完成後統一清理掉全部被標記的對象。以下圖:命令行
該算法會有如下兩個問題:
標記清除算法會產生內存碎片,而複製算法須要有額外的內存擔保空間,因而針對老年代的特色,又有了標記整理算法。標記整理算法的標記過程與標記清除算法相同,但後續步驟再也不對可回收對象直接清理,而是讓全部存活的對象都向一端移動,而後清理掉這一端邊界之外的內存。以下圖:
在方法區進行垃圾回收通常「性價比」較低,由於在方法區主要回收兩部份內容:廢棄常量 和 無用的類 。
回收廢棄常量與回收其餘年代中的對象相似。
可是判斷一個類是否是無用的類的條件則至關苛刻:
但即便知足以上條件也未必必定會回收,Hotspot VM還提供了-Xnoclassgc參數控制(關閉CLASS的垃圾回收功能)。所以在大量使用動態代理、CGLib等字節碼框架的應用中必定要關閉該選項,開啓VM的類卸載功能,以保證方法區不會溢出。
System.gc()方法 和 finalize()方法
使用System.gc() 能夠無論JVM使用的是哪種垃圾回收的算法,均可以請求Java的垃圾回收。在命令行中有一個參數-verbosegc
能夠查看Java使用的堆內存的狀況,它的格式以下:java -verbosegc classfile
因爲這種方法會影響系統性能,不推薦使用,因此不詳細介紹。
JVM垃圾回收器在回收一個對象以前,通常要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的狀況下,Java提供了缺省機制來終止該對象從而釋放資源,這個方法就是finalize() 。它的原型爲:protected void finalize() throws Throwable
在finalize() 方法返回以後,對象消失,垃圾收集開始執行。原型中的throws Throwable 表示它能夠拋出任何類型的異常。
之因此要使用finalize(),是由於存在着垃圾回收器不能處理的特殊狀況。例如:
因爲在分配內存的時候可能採用了相似 C語言的作法,而非Java一般的new 。這種狀況主要發生在 native 方法中,好比 native 方法調用了C/C++
的malloc()
函數來分配存儲空間,除非調用 free() 函數,不然這些內存空間將不會獲得釋放,那麼這個時候就可能形成內存泄漏。可是因爲 free() 是C/C++
中的函數,因此 finalize() 中能夠用本地方法來調用它。以釋放這些「特殊」的內存空間。
或者是打開的文件資源,這些資源不屬於垃圾回收器的回收範圍。
GC在優先級最低的線程中運行,通常在應用程序空閒即沒有應用線程在運行時被調用。但下面的條件例外。
Java堆內存不足時,GC會被調用。當應用線程在運行,並在運行過程當中建立新對象,若這時內存空間不足,JVM就會強制調用GC線程。若GC一次以後仍不能知足內存分配,JVM會再進行兩次GC,若仍沒法知足要求,則JVM將報「out of memory」的錯誤,Java應用將中止。
不要顯式調用System.gc() 。此函數建議JVM進行主GC,雖然只是建議而非必定,但不少狀況下它會觸發主GC,從而增長主GC的頻率,也即增長了間歇性停頓的次數。大大的影響系統性能。
儘可能減小臨時對象的使用。臨時對象在跳出函數調用後,會成爲垃圾,少用臨時變量就至關於減小了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減小了主GC的機會。
對象不用時最好顯式置爲Null。通常而言,爲Null的對象都會被做爲垃圾處理,因此將不用的對象顯式地設爲Null,有利於GC收集器斷定垃圾,從而提升了GC的效率。
儘可能使用StringBuffer,而不用String來累加字符串。因爲String是固定長的字符串對象,累加String對象時,並不是在一個String對象中擴增,而是從新建立新的String對象,如 Str5=Str1+Str2+Str3+Str4;
這條語句執行過程當中會產生多個垃圾對象,由於每次做「+」操做時都必須建立新的String對象,但這些過渡對象對系統來講是沒有實際意義的,只會增長更多的垃圾。避免這種狀況能夠改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。
能用基本類型如 int, long
就不用包裝類型Integer, Long
。基本類型變量佔用的內存資源比包裝類型佔用的少得多,若是沒有必要,最好使用基本變量。
儘可能少用靜態對象變量。靜態變量屬於全局變量,不會被GC回收,它們會一直佔用內存。
分散對象建立或刪除的時間。集中在短期內大量建立新對象,特別是大對象,會致使忽然須要大量內存,JVM在面臨這種狀況時,只能進行主GC,以回收內存或整合內存碎片,從而增長主GC的頻率。集中刪除對象,道理也是同樣的。它使得忽然出現了大量的垃圾對象,空閒空間必然減小,從而大大增長了下一次建立新對象時強制主GC的機會。
在JDK7中,有5種垃圾收集器:
其中,Serial 收集器必定不能用於服務器端。這個收集器類型僅應用於單核CPU桌面電腦。使用Serial收集器會顯着下降應用程序的性能。