java垃圾回收



1. 垃圾回收的意義

靜態內存分配和回收

主要是靜態變量,string ,,靜態代碼塊的分配,直到程序實行結束時內存才被回收。是在棧上分配的

對象存儲的堆中,


GC完成的兩件事

1.能夠正確的檢測出垃圾對象

2.能夠釋放垃圾對象佔用的內存空間

2.垃圾收集算法

根對象垃圾算法:


從跟對象開始搜索,如果不能被到達的節點,則視爲垃圾對象,即可回收。根集合對象裏面主要是一些本地方法,java棧等對 對象的引用。

基於分代的垃圾收集算法:

把對象按照壽命長短來分組,分爲年輕代和年老代,新創建的對象被分在年輕代,如果對象經過幾次回收後仍然存在的話,那麼再把這個對象劃分到年老代。年老代的手機頻率沒有年輕代更新頻繁,減少了每次垃圾收集時所掃描的對象數量,從而提高垃圾回收率。

-Xmx1024m -Xms10240m -Xmn5120m -XXSurvivorRatio=3
,其最小內存值和Survivor區總大小分別是()

-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn:年輕代大小
-XXSurvivorRatio:年輕代中Eden區與Survivor區的大小比值
年輕代5120m, Eden:Survivor=3,Survivor區大小=1024mSurvivor區有兩個,即將年輕代分爲5份,每個Survivor區佔一份),總大小爲2048m。
-Xms初始堆大小即最小內存值爲10240m

引用計數法算法

引用計數法是唯一沒有使用根集的垃圾回收的法,堆中的每個對象對應一個引用計數器。當每一次創建一個對象並賦給一個變量時,引用計數器置爲1。當對象被賦給任意變量時,引用計數器每次加1當對象出了作用域後(該對象丟棄不再使用),引用計數器減1,一旦引用計數器爲0,對象就滿足了垃圾收集的條件。
  基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜地必須實時運行的程序。但引用計數器增加了程序執行的開銷,因爲每次對象賦給新的變量,計數器加1,而每次現有對象出了作用域生,計數器減1


3.減少GC開銷的措施

1)不要顯式調用System.gc()

(2)儘量減少臨時對象的使用

(3)對象不用時最好顯式置爲Null

4)儘量使用StringBuffer,而不用String來累加字符串

(5)能用基本類型如Int,Long,就不用Integer,Long對象

(6)儘量少用靜態對象變量

(7)分散對象創建或刪除的時間


對於GC 線程執行前後內存地址發生了變化的對象如何對其保持引用

如果要是你設計這麼一個東西你會如何做?
答:做一個對象引用到地址的映射,讓GC 線程在移動對象的時候負責更新地址


2. 垃圾收集的算法分析
  Java語言規範沒有明確地說明JVM使用哪種垃圾回收算法,但是任何一種垃圾回收算法一般要做2件基本的事情:(1)發現無用信息對象;(2)回收被無用對象佔用的內存空間,使該空間可被程序再次使用。
  大多數垃圾回收算法使用了根集(root set)這個概念;所謂根集就是正在執行的Java程序可以訪問的引用變量的集合(包括局部變量、參數、類變量),程序可以使用引用變量訪問對象的屬性和調用對象的方法。垃圾回收首先需要確定從根開始哪些是可達的和哪些是不可達的,從根集可達的對象都是活動對象,它們不能作爲垃圾被回收,這也包括從根集間接可達的對象。而根集通過任意路徑不可達的對象符合垃圾收集的條件,應該被回收。下面介紹幾個常用的算法。


2.1. 引用計數法(Reference Counting Collector)
  引用計數法是唯一沒有使用根集的垃圾回收的法,該算法使用引用計數器來區分存活對象和不再使用的對象。一般來說,堆中的每個對象對應一個引用計數器。當每一次創建一個對象並賦給一個變量時,引用計數器置爲1。當對象被賦給任意變量時,引用計數器每次加1當對象出了作用域後(該對象丟棄不再使用),引用計數器減1,一旦引用計數器爲0,對象就滿足了垃圾收集的條件。
  基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜地必須實時運行的程序。但引用計數器增加了程序執行的開銷,因爲每次對象賦給新的變量,計數器加1,而每次現有對象出了作用域生,計數器減1。

2.5.generation算法(Generational Collector)
  stop-and-copy垃圾收集器的一個缺陷是收集器必須複製所有的活動對象,這增加了程序等待時間,這是coping算法低效的原因。在程序設計中有這樣的規律:多數對象存在的時間比較短,少數的存在時間比較長。因此,generation算法將堆分成兩個或多個,每個子堆作爲對象的一代 (generation)。由於多數對象存在的時間比較短,隨着程序丟棄不使用的對象,垃圾收集器將從最年輕的子堆中收集這些對象。在分代式的垃圾收集器運行後,上次運行存活下來的對象移到下一最高代的子堆中,由於老一代的子堆不會經常被回收,因而節省了時間。

4. finalize()方法

      在JVM垃圾回收器收集一個對象之前,一般要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的情況下,Java提供了缺省機制來終止該對象心釋放資源,這個方法就是finalize()。它的原型爲:
  protected void finalize() throws Throwable
  在finalize()方法返回之後,對象消失,垃圾收集開始執行。原型中的throws Throwable表示它可以拋出任何類型的異常。
  之所以要使用finalize(),是存在着垃圾回收器不能處理的特殊情況。假定你的對象(並非使用new方法)獲得了一塊“特殊”的內存區域,由於垃圾回收器只知道那些顯示地經由new分配的內存空間,所以它不知道該如何釋放這塊“特殊”的內存區域,那麼這個時候java允許在類中定義一個由finalize()方法。

      特殊的區域例如:1)由於在分配內存的時候可能採用了類似 C語言的做法,而非JAVA的通常new做法。這種情況主要發生在native method中,比如native method調用了C/C++方法malloc()函數系列來分配存儲空間,但是除非調用free()函數,否則這些內存空間將不會得到釋放,那麼這個時候就可能造成內存泄漏。但是由於free()方法是在C/C++中的函數,所以finalize()中可以用本地方法來調用它。以釋放這些“特殊”的內存空間。2)又或者打開的文件資源,這些資源不屬於垃圾回收器的回收範圍。
      換言之,finalize()的主要用途是釋放一些其他做法開闢的內存空間,以及做一些清理工作。因爲在JAVA中並沒有提夠像“析構”函數或者類似概念的函數,要做一些類似清理工作的時候,必須自己動手創建一個執行清理工作的普通方法,也就是override Object這個類中的finalize()方法。例如,假設某一個對象在創建過程中會將自己繪製到屏幕上,如果不是明確地從屏幕上將其擦出,它可能永遠都不會被清理。如果在finalize()加入某一種擦除功能,當GC工作時,finalize()得到了調用,圖像就會被擦除。要是GC沒有發生,那麼這個圖像就會

被一直保存下來。

      一旦垃圾回收器準備好釋放對象佔用的存儲空間,首先會去調用finalize()方法進行一些必要的清理工作。只有到下一次再進行垃圾回收動作的時候,纔會真正釋放這個對象所佔用的內存空間。
  在普通的清除工作中,爲清除一個對象,那個對象的用戶必須在希望進行清除的地點調用一個清除方法。這與C++"析構函數"的概念稍有牴觸。在C++中,所有對象都會破壞(清除)。或者換句話說,所有對象都"應該"破壞。若將C++對象創建成一個本地對象,比如在堆棧中創建(在Java中是不可能的,Java都在堆中),那麼清除或破壞工作就會在"結束花括號"所代表的、創建這個對象的作用域的末尾進行。若對象是用new創建的(類似於Java),那麼當程序員調用C++的 delete命令時(Java沒有這個命令),就會調用相應的析構函數。若程序員忘記了,那麼永遠不會調用析構函數,我們最終得到的將是一個內存"漏洞",另外還包括對象的其他部分永遠不會得到清除。
  相反,Java不允許我們創建本地(局部)對象--無論如何都要使用new。但在Java中,沒有"delete"命令來釋放對象,因爲垃圾回收器會幫助我們自動釋放存儲空間。所以如果站在比較簡化的立場,我們可以說正是由於存在垃圾回收機制,所以Java沒有析構函數。然而,隨着以後學習的深入,就會知道垃圾收集器的存在並不能完全消除對析構函數的需要,或者說不能消除對析構函數代表的那種機制的需要(原因見下一段。另外finalize()函數是在垃圾回收器準備釋放對象佔用的存儲空間的時候被調用的,絕對不能直接調用finalize(),所以應儘量避免用它)。若希望執行除釋放存儲空間之外的其他某種形式的清除工作,仍然必須調用Java中的一個方法。它等價於C++的析構函數,只是沒後者方便。
      在C++中所有的對象運用delete()一定會被銷燬,而JAVA裏的對象並非總會被垃圾回收器回收。In another word, 1 對象可能不被垃圾回收,2 垃圾回收並不等於“析構”,3 垃圾回收只與內存有關。也就是說,並不是如果一個對象不再被使用,是不是要在finalize()中釋放這個對象中含有的其它對象呢?不是的。因爲無論對象是如何創建的,垃圾回收器都會負責釋放那些對象佔有的內存

6. 減少GC開銷的措施

  根據上述GC的機制,程序的運行會直接影響系統環境的變化,從而影響GC的觸發。若不針對GC的特點進行設計和編碼,就會出現內存駐留等一系列負面影響。爲了避免這些影響,基本的原則就是儘可能地減少垃圾和減少GC過程中的開銷。具體措施包括以下幾個方面:

(1)不要顯式調用System.gc()

  此函數建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數。

(2)儘量減少臨時對象的使用

  臨時對象在跳出函數調用後,會成爲垃圾,少用臨時變量就相當於減少了垃圾的產生,從而延長了出現上述第二個觸發條件出現的時間,減少了主GC的機會。

(3)對象不用時最好顯式置爲Null

  一般而言,爲Null的對象都會被作爲垃圾處理,所以將不用的對象顯式地設爲Null,有利於GC收集器判定垃圾,從而提高了GC的效率。

(4)儘量使用StringBuffer,而不用String來累加字符串

  由於String是固定長的字符串對象,累加String對象時,並非在一個String對象中擴增,而是重新創建新的String對象,如Str5=Str1+Str2+Str3+Str4,這條語句執行過程中會產生多個垃圾對象,因爲對次作“+”操作時都必須創建新的String對象,但這些過渡對象對系統來說是沒有實際意義的,只會增加更多的垃圾。避免這種情況可以改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。

(5)能用基本類型如Int,Long,就不用Integer,Long對象

  基本類型變量佔用的內存資源比相應對象佔用的少得多,如果沒有必要,最好使用基本變量。

(6)儘量少用靜態對象變量

  靜態變量屬於全局變量,不會被GC回收,它們會一直佔用內存。

(7)分散對象創建或刪除的時間

  集中在短時間內大量創建新對象,特別是大對象,會導致突然需要大量內存,JVM在面臨這種情況時,只能進行主GC,以回收內存或整合內存碎片,從而增加主GC的頻率。集中刪除對象,道理也是一樣的。它使得突然出現了大量的垃圾對象,空閒空間必然減少,從而大大增加了下一次創建新對象時強制主GC的機會



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章