垃圾收集器和內存分配策略

什麼是垃圾收集器java

顧名思義,垃圾收集器就是用來收集垃圾的「機器(程序)」。以前我也很疑惑,既然垃圾回收都進入了自動化的時代了,都鳥槍換炮了,爲何咱們還要去了解垃圾收集器呢。答案其實很簡單,就是垃圾回收的算法還不夠智能,它不可以知足全部的需求,因此有的的時候咱們須要根據實際中的需求手動調教一下垃圾收集器,讓他更好的配合咱們的工做。可是因爲垃圾收集器種類也不少,每一個種類的垃圾收集器的特色各有不一樣。這叫像是在訓狗,你要充分了解不一樣狗種之間的習性特色,才能因材施教,對症下藥,讓狗狗聽你的話。不然,在不了狗狗的狀況下胡亂的訓練,可能致使訓狗不成反被狗咬的悲慘下場。算法

因此,咱們想要熟練地使用垃圾收集器,而且想要他們可以更好更高效的工做,首先要作就是了解各類垃圾收集器的特色、主要配置參數和使用場景。框架

 

垃圾收集器和內存區域代理

垃圾收集器和內存區域是緊密相連的,由於垃圾收集器的主要做用簡單來講就是在內存區域內回收已經再也不使用的內存。而對於java語言來講,這毫無疑問是把「矛頭」指向了java對象實例,而java中大部分的對象實例都是分配在java堆上的。因此,能夠說,垃圾收集器就是創建在java堆上的。固然,這是一種籠統的說法,其實在方法區也有垃圾回收的。對象

 

判斷對象的死亡內存

如何判斷對象的死亡,前提是你得先有一個對象。要進行垃圾回收,必需要先判斷哪些對象是能夠回收的,哪些對象是不能回收的。你不能亂來,對於現實世界來講,亂世可能出英雄;可是,對於程序世界來講,亂世只能出悲劇。因此,垃圾回收不能胡來。能夠把垃圾回收想象成一個火葬場,對於屍體的焚燒,首先要判斷一下屍體是否已經死亡。因此,這裏要講的就是如何判斷屍體已經死亡,是沒有呼吸了?仍是沒有心跳了?仍是沒有腦電波?虛擬機

  1. 引用計數法

引用計數法比較簡單。就是給每個對象保存一個計數器,當有地方引用這個對象的時候,這個計數器就加1;反之,當有一個引用失效的時候就減1。接着上面的比喻,假如火葬場判斷屍體有沒有死亡,是經過觀看有沒有人拉着屍體的手來決定的。若是有人拉着屍體的手,表示這個屍體還有用,不能立馬焚燒。若是沒有人拉着屍體手,表示這個屍體已經毫無用處,在這個世界上已經沒有任何留戀了,能夠安心的離開了這個世界了,因此能夠垃圾焚燒。這原本也沒有什麼問題,直到有一天,送來兩具手拉着手的實體。自動化

因此說,儘管引用計數法在判斷對象是否死亡的問題上簡單粗暴,可是也正是因爲它簡單粗暴的緣由。它沒法解決對象互相引用的問題,也就是說兩個都已經死亡的對象互相引用,致使計算器始終沒法是0,以致於這兩個對象一直沒法被回收。這就是咱們常說的,對於引用計算法致使的循環引用的問題。效率

2.可達性分析算法變量

因爲引用計算法存在循環引用的問題,因此主流的商用語言都是毅然決然地拋棄上面的這種方法,而選擇了可達性分析來判斷對象是否存活。這個算法名字聽起來挺唬人的, 其實基本的思想就是經過一系列被稱之爲「GC Roots」的對象做爲起始點,向下搜索,搜索所走過的路徑稱之爲引用鏈(reference Chain),當一個對象沒有在任何一個GC Roots的引用鏈上時,咱們就說這個對象是不可用的,也就是死亡的。

什麼意思呢?仍是用上面的例子,引用計算法是看全部的屍體手有沒有被人拉着。可達性分析算法是看活着的人有沒有拉着屍體。也就是說,找一些確定是活人的人,經過這些活人去證實其餘人是否是活着。就好像六度空間理論同樣,這個世界上全部活着人能夠組成一個關係網,就像你和你老婆有關係,你老婆又和隔壁老王有關係,那間接的你和隔壁老王就產生了一個關係。而那些經過這種我認識你你認識他也沒法找到的人,也就是說,不在這個世界上任何一個關係網的人,咱們就能夠當他已是一個死人了。

兩種判斷對象是否死亡的方法其實最大的不一樣是:引用計數從全部對象查找有沒有被引用,這裏並無區分活着的仍是死亡的對象,因此它沒法區別兩個死亡的對象相互引用的問題;而可達性分析算法是從必定活着的對象分析,只要經過活着的對象可以到達的對象,咱們就能夠當作的活着的對象。那些不可達的,就能夠判斷他壽終正寢了。這種能夠避免循環引用的問題。

因此,如今主流的算法都是使用的可達性分析算法,在java語言中能夠被當作GC Roots的對象包括如下幾種:

  • 虛擬機棧中引用的對象(棧中的本地變量表)
  • 方法區中類靜態屬性引用的對象
  • 方法區中經常使用引用的對象
  • 本地方法棧中JNI應用的對象(即通常說的Native對象)

 

 

生存仍是死亡這是一個問題

對於引用這個概念,在實際的過程當中,並非那麼純粹。按照實際的使用中,有些對象雖然被引用了,可是其實他們沒有太多的做用,是能夠被回收的。這些對象就像是雞肋,食之無味,可是棄之惋惜。因此,對於這些對象,咱們但願他在內存緊張的時候能夠被回收,可是在內存富裕的狀況下,咱們姑且暫時不處理他,對於這樣的需求,JDK給引用又分了好多的類型以下:

  1. 強引用:相似於「Object obj = new Object()」這種,只要引用存在,就不能被回收。
  2. 軟引用:這種引用的對象是非必需的,在內存緊張的時候,即將要發生內存溢出的以前,進行回收。經過SolfReference類來實現軟引用。
  3. 弱引用:比軟引用還要若,這種引用只能挺過一次垃圾回收,在第二次垃圾回收的時候,回收回收。經過WeakReference類來實現弱引用
  4. 虛引用:又稱之爲幽靈引用或者幻影引用。這種引用對於生存回收徹底沒有影響,也就是說這種引用在第一次垃圾回收的時候就會被幹掉。他的目的就是能在這個對象被回收的時候收到一個系統的通知。經過PhantomReference類來實現。

 

另外,在Object類中,也就是全部類的父類中,有一個finalize()方法。虛擬機在回收對象的時候,該對象已經沒有在任何GC Root中了,虛擬機會查看該對象有沒有覆蓋和執行過這個方法。若是該對象沒有覆蓋該方法或者該對象已經覆蓋而且執行過該方法,那麼這個對象纔會被宣告死亡。也就是說,經過這個方法,可讓對象暫且免於一死,由於咱們能夠在這個方法第一次執行的時候,從新把這個對象放在GC Root中,這樣在下次GC的時候,該對象又出如今GC Root中,從而免於一死。等於給本身放了一塊免死金牌。

 

再者,在方法區也會進行回收的,好比說方法區的類,在大量使用了反射、動態代理、GCLib等ByteCode框架、動態生成JSP以及OSGI這類頻繁自定義ClassLoader的場景中,都須要虛擬機具有有類卸載的功能,以保證永久代不會溢出。知足下面三中條件的類纔算是無用類:

  1. 該類的全部實例已經被回收
  2. 加載該類的ClassLoader已經被回收
  3. 該類對應的java.lang.Class對象已有在任何地方被引用,沒法再任何地方經過反射訪問該類。

 

垃圾收集算法

垃圾回收算法涉及到大量的程序細節,因此咱們基本上就是簡單瞭解一下幾種算法的思想及其發展過程就好了。下面講講幾種主流的垃圾回收的算法

  1. 標記-清除算法

基本思想就是先標記出須要清除的對象,而後統一回收全部被標記的對象。他是最基本的收集算法,由於後續的手機算法都是基於這種思想並對其不足進行改進而獲得的。

這種算法主要有兩個缺點「

  • 效率不高
  • 會產生內存碎片,內存碎片過多會致使之後在分配較大對象時,沒法找到足夠連續的內存單元而不但不提早出發一次垃圾回收動做。

2.複製算法

這種算法出現就是爲了防止初上標記-清除算法會產生內存碎片的問題。這種算法的基本思想是把內存分爲大小相等的兩塊內存,每次只使用其中的一塊。當這一塊內存使用完以後,就將還存活的對象複製到另外一塊上面,而後再把這塊內存空間清除掉。這樣每次都對整個半區進行內存回收,不會產生內存碎片。這種算法的代價上就是將內存縮小到原來的一半,代價是至關的高。

如今商業虛擬機都是採用這種收集算法來回收新生代。IBM公司的專門研究代表,新生代中的對象98%都是朝生夕死,因此並不須要按照1:1的比例來分配內存空間。能夠將內存劃分爲一塊較大的Eden空間和兩塊較小的survive空間,每次使用Eden和Survive中的一塊。(HotSpot就是這種策略)當回收時,將Eden和Survive中還存活的對象,一次性地複製到另外一塊Survive空間上,最後清理掉Eden和剛纔使用過的Survive空間。有的時候,當survive內存空間不足時,咱們還能夠依賴其餘內存好比說老年代的內存空間進行擔保。這樣作的目的是不用額發出發一次GC。

注意:複製算法如今有兩個內存區域,一個是Eden,兩個是survive,這三者之間有什麼關係呢?在這兩個survive中,一個名字叫作to,而另外一個名字叫作from。在GC開始的以前,對象只在eden和from兩個內存空間中存在,而to是空的;在GC開始的時候,Eden中還存活的對象會複製到to Survive中,而from中還存活的對象按照年齡(年齡閾值,能夠經過-XX:MaxTenuringThreshold來設置)來區分,達到年齡閾值的會進入到老年區;而尚不足以達到年齡閾值的對象會進入到to Survive中。這時候原來to survive 和 from survive的角色互換,也就是說以前的to survive變成了 from survive,而以前的from survive變成了to survive。GC完成的時候,Eden和如今的to survive已經清空,存在的對象都在from survive中

3.標記-整理的算法

複製算法對於存活率較低的新生代內存空間比較有效,可是對於對象存活率比較高的老年代來講,就不是很樂觀了。由於複製算法要進行大量的複製操做,效率會變得很是低。因此咱們在老年代使用另外一種算法,就是這裏要講的標記-整理的算法。這種算法的思想就是,讓已經存活的對象都向一端移動,移動完成後,直接清理掉邊界以外的內存區域就好。這種算法不須要大量的複製操做,因此對象存活率比較高的老年代老說,比較合適。

相關文章
相關標籤/搜索