本文目錄結構html
java虛擬機在執行java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。 java
咱們注意到運行時區域主要會包括5部分區域,它們有個各自的用途,以及建立和銷燬時間,有的依賴虛擬機進程,有的依賴用戶線程。在java虛擬機棧中咱們提到局部變量表存放了對象的引用,咱們都知道對象是分配的java堆中的,那麼具體是怎麼引用的呢? 好比Object obj = new Object();,假設這句代碼出如今方法體中,那麼「Object obj 」這部分語義將會反映到java棧的本地變量表中(爲reference類型),而「new Object()」這部分語義將會反映在java堆上,造成一塊存儲了Object類型全部實例數據值的結構化內存。 因爲reference類型在java虛擬機規範中只規定了一個指向對象的引用,因此在實際虛擬機中訪問會有所不一樣,主流訪問有兩種:git
java堆會劃分出一小塊內存空間做爲句柄池,reference中存儲的就是對象的句柄地址,二句柄中包含了對象實例數據和類型數據的各自地址信息。 github
reference中直接存儲的就是對象的地址,java堆須要考慮對象的佈局中如何存放訪問類型數據的相關信息。 算法
這兩種對象的訪問方式各有優點,使用句柄訪問方式的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動時只會改變句柄中的實例數據指針,而reference自己不須要被修改。使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的的時間開銷。GC在對堆內存進行回收前,第一件事是須要肯定哪些對象是須要被回收的,因此就須要判斷對象是否存活。通常的有兩種方法來判斷:數據結構
前面已經提到方法區是不多出現垃圾收集器的,由於方法區回收的性價比比較低,一般堆內存的回收一次能夠回收70%-95%的空間,但方法區的垃圾收集器效率很低。 通常的,方法區回收主要由兩部分: 1.廢棄常量 廢棄的常量與堆回收比較相似,只須要指導該常量是否在其餘地方被使用便可。 2.無用的類 這種狀況的判斷比較苛刻,通常要求知足如下三個條件纔算是無用的: a. 該類的全部實例都被回收 b. 加載該類的ClassLoader也被回收 c. 該類對應的java.lang.class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類多線程
1.標記-清除算法 最基礎的收集算法是「標記-清除」(Mark-Sweep)算法,如同它的名字同樣,算法分爲「標記」和「清除」兩個階段。 a. 首先標記出全部須要回收的對象 b. 在標記完成後統一回收全部被標記的對象。 缺點: 效率問題:標記和清除兩個過程的效率都不高 空間問題:標記清除以後產生大量不連續的內存碎片,空間碎片太多可能會致使之後程序運行過程當中須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。 佈局
2.複製算法 目的是爲了解決效率問題。 將可用內存按容量大小劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊內存使用完了,就將還存活着的對象複製到另外一塊上面,而後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況。 缺點: 將內存縮小爲了原來的一半。 post
現代的商業虛擬機都採用這種收集算法來回收新生代,IBM公司的專門研究代表,新生代中對象98%對象是「朝生夕死」的,因此不須要按照1:1的比例來劃份內存空間,而是將內存分爲較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。3.標記-整理算法 複製收集算法在對象存活率較高時,就要進行較多的複製操做,效率就會變低。 根據老年代的特色,提出了「標記-整理」算法。 標記過程仍然與」標記-清除「算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存。 線程
4.分代收集算法 通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法。在老年代中,由於對象存活率高、沒有額外空間對它進行分配擔保,就必須採用「標記-清除」或「標記-整理」算法來進行回收。JVM把年輕代分爲了三部分:1個Eden區和2個Survivor區(分別叫from和to),默認比例爲8:1。 工做過程:通常狀況下,新建立的對象都會被分配到Eden區(一些大對象特殊處理),這些對象通過第一次GC後,若是仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次GC,年齡就會增長1歲,當它的年齡增長到必定程度時,就會被移動到年老代中。 由於年輕代中的對象基本都是朝生夕死的(80%以上),因此在年輕代的垃圾回收算法使用的是複製算法,複製算法不會產生內存碎片。在GC開始的時候,對象只會存在於Eden區和名爲「From」的Survivor區,Survivor區「To」是空的。緊接着進行GC,Eden區中全部存活的對象都會被複制到「To」,而在「From」區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到必定值(年齡閾值,能夠經過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被複制到「To」區域。通過此次GC後,Eden區和From區已經被清空。這個時候,「From」和「To」會交換他們的角色,也就是新的「To」就是上次GC前的「From」,新的「From」就是上次GC前的「To」。無論怎樣,都會保證名爲To的Survivor區域是空的。GC會一直重複這樣的過程,直到「To」區被填滿,「To」區被填滿以後,會將全部對象移動到年老代中。
先了解下Minor GC與Major GC/Full GC
在發生Minor GC時,虛擬機會檢測以前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,若是大於,則改成直接進行一次Full GC,若是小於,則查看HandlePromotionFailure設置是否容許擔保失敗,若是容許,那麼只會進行Minor GC,若是不容許,那麼進行一次Full GC。 在分代回收算法中提到過,新生代使用複製收集算法,但爲了內存利用率,只使用其中一個Survivor空間來做爲輪換備份,所以當出現大量對象在Minor GC後仍然存活的狀況(最極端的狀況就是內存回收後新生代中全部對象都存活),就須要老年代進行分配擔保,把Survivor沒法容納的對象直接進入老年代。與生活中的貸款擔保相似,老年代要進行這樣的擔保,前提是老年代自己還有容納這些對象的剩餘空間,一共有多少對象會活下來在實際完成內存回收以前是沒法明確知道的,因此只好取以前每一次回收晉升到老年代對象容量的平均大小值做爲經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。
@Dpuntu, 本文版權屬於再惠研發團隊,歡迎轉載,轉載請保留出處。