對於垃圾收集(GC), 咱們須要考慮三件事情:哪些內存須要回收?如何判斷是垃圾對象?垃圾回收算法有哪些?html
一、不是GC的工做區域java
(1)程序計數器、虛擬機棧和本地方法棧三個區域是線程私有的,隨線程生而生,隨線程滅而滅;算法
(2)棧中的棧幀隨着方法的進入和退出而進行入棧和出棧操做,每一個棧幀中分配多少內存基本上是在類結構肯定下來時就已知的,所以這幾個區域的內存分配和回收都具備肯定性。性能
在這幾個區域不須要過多考慮回收的問題,由於方法結束或線程結束時,內存天然就跟隨着回收了。優化
二、GC的工做區域(哪些內存須要GC回收?)spa
(1)垃圾回收重點關注的是堆和方法區部分的內存。線程
由於一個接口中的多個實現類須要的內存可能不同,一個方法的多個分支須要的內存也可能不同,咱們只有在程序處於運行期間才能知道會建立哪些對象,這部份內存的分指針
配和回收都是動態的,因此垃圾回收器所關注的主要是這部分的內存。htm
Java堆中存放着幾乎全部的對象實例,垃圾收集器對堆中的對象進行回收前,要先肯定這些對象是否還有用,哪些還活着。對象死去的時候才須要回收。對象
引用計數法的邏輯是:在堆中存儲對象時,在對象頭處維護一個counter計數器,若是一個對象增長了一個引用與之相連,則將counter++。
若是一個引用關係失效則counter–。若是一個對象的counter變爲0,則說明該對象已經被廢棄,不處於存活狀態。
優勢
1)可即刻回收垃圾,每一個對象都知道本身的被引用數,當counter爲0時,對象就會把本身做爲空閒空間鏈接到空閒鏈表,也就是在對象變成垃圾的同時就會被回收.
2)最大暫停時間短,每次經過指向mutator生成垃圾時,這部分垃圾都會被回收,大幅削減了mutator的最大暫停時間。
缺點
1)引用和去引用伴隨加法和減法,影響性能
2)很難處理循環引用
這種算法的基本思路是經過一系列名爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,就證實此對象是不可用的。
Java語言是經過可達性分析算法來判斷對象是否存活的。
在Java語言裏,可做爲GC Roots的對象包括下面幾種:
(1)虛擬機棧(棧幀中的本地變量表)中引用的對象。
(2)方法區中的類靜態屬性引用的對象。
(3)方法區中的常量引用的對象。
(4)本地方法棧中JNI(Native方法)的引用對象。
簡單來講有兩個步驟:標記、清除。
(1). 標記階段:找到全部可訪問的對象,作個標記
(2). 清除階段:遍歷堆,把未被標記的對象回收
缺 點
(1)由於涉及大量的內存遍歷工做,因此執行性能較低,這也會致使「stop the world」時間較長,java程序吞吐量下降;
(2)對象被清除以後,被清除的對象留下內存的空缺位置會形成內存不連續,空間浪費。
標記-整理算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上作了一些優化。
(1)、標記階段:它的第一個階段與標記/清除算法是如出一轍的。
(2)、整理階段:移動全部存活的對象,且按照內存地址次序依次排列,而後將末端內存地址之後的內存所有回收。
上圖中能夠看到,標記的存活對象將會被整理,按照內存地址依次排列,而未被標記的內存會被清理掉。如此一來,當咱們須要給新對象分配內存時,JVM只須要持有一個內存的起始地址便可,這比維護一個空閒
列表顯然少了許多開銷。
優勢
標記/整理算法不只能夠彌補標記/清除算法當中,內存區域分散的缺點,也消除了複製算法當中,內存減半的高額代價。
缺點
標記/整理算法惟一的缺點就是效率也不高。不只要標記全部存活對象,還要整理全部存活對象的引用地址。從效率上來講,標記/整理算法要低於複製算法。
複製算法簡單來講就是把內存一分爲二,但只使用其中一份,在垃圾回收時,將正在使用的那分內存中存活的對象複製到另外一份空白的內存中,最後將正在使用的內存空間的對象清除,完成垃圾回收。
優勢
複製算法使得每次都只對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
缺點
複製算法的代價是將內存縮小爲原來的一半,這個太要命了。
注意(重要)
如今的虛擬機使用複製算法來進行新生代的內存回收。由於在新生代中絕大多數的對象都是「朝生夕亡」,因此不須要將整個內存分爲兩個部分,而是分爲三個部分,一塊爲Eden(伊麪區)和兩塊較小的
Survivor(倖存區)空間(默認比例->8:1:1)。每次使用Eden和其中的一塊Survivor,垃圾回收時候將上述兩塊中存活的對象複製到另一塊Survivor上,同時清理上述Eden和Survivor。因此每次新生代就可使用90%
的內存。只有10%的內存是浪費的。(不能保證每次新生代都少於10%的對象存活,當在垃圾回收複製時候若是一塊Survivor不夠時候,須要老年代來分擔,大對象直接進入老年代)
總的來說:複製算法不適用於存活對象較多的場合,如老年代(複製算法適合作新生代的GC)
相同點
(1)三個算法都基於根搜索算法去判斷一個對象是否應該被回收,而支撐根搜索算法能夠正常工做的理論依據,就是語法中變量做用域的相關內容。
(2)在GC線程開啓時,或者說GC過程開始時,它們都要暫停應用程序(stop the world)。
區別
三種算法比較:
效率:複製算法>標記-整理算法>標記-清除算法;
內存整齊度:複製算法=標記-整理算法>標記-清除算法
內存利用率:標記-整理算法=標記-清除算法>複製算法
首先這不是一種新算法,它是一種思想。如今使用的Java虛擬機並非只是使用一種內存回收機制,而是分代收集的算法。就是將內存根據對象存活的週期劃分爲幾塊。通常是把堆分爲新生代、和老年代。短命對
象存放在新生代中,長命對象放在老年代中。
這個圖是我拷貝來的,但要記住java8之後,已經沒有永久區了,以前永久區存放的東西基本上放到了元空間中。
對於不一樣的代,採用不一樣的收集算法:
新生代:因爲存活的對象相對比較少,所以能夠採用複製算法該算法效率比較快。
老年代:因爲存活的對象比較多哈,能夠採用標記-清除算法或是標記-整理算法。
想太多,作太少,中間的落差就是煩惱。想沒有煩惱,要麼別想,要麼多作。少校【16】