JVM自動內存管理之內存分配

最近看了深入理解java虛擬機,先說一下看後的感覺。

內存回收和垃圾收集器這兩個東西都直接影響系統的性能,但是這些東西不都是自動完成的嗎?爲什麼我們要學它?我們學了這個東西可以幹什麼,這個我想都是大家關注的。我這裏舉一個例子。

假設我是一個打乒乓球的,我是拿橫拍的,我正面膠皮喜歡用“狂飆”,反面喜歡用生膠,底板喜歡用蝴蝶牌的,那我就需要單獨去買上述的幾個東東,然後自己“組裝”成乒乓球拍。
當然乒乓球拍生產廠商提供了多種類型的成品拍(就是買來就可以直接打的那種)。

對於新手,那可能就直接買一塊成品拍(對膠皮和底板暫時沒有特殊的要求),像我這樣的乒乓球高手肯定就需要個性化的配置。這個跟虛擬機的各種參數的選擇,垃圾回收器的選擇其實也是一個道理:
jvm部署好了之後,那些可供選擇的參數都有一個初始的默認值,如果你這個時候剛剛起步學java就像剛剛開始打乒乓球一樣,jvm默認參數是什麼,我們就用什麼,反正我們的水平還達不到需要care下面虛擬機裏面到底是什麼怎樣運行的。

但是隨着學習的深入,你可能會慢慢地變得對性能,高併發時資源使用情況有興趣了,你就有必要了解一下jvm到底是如何運作的,jvm提供了各種各樣的參數給我們,不就是讓我們根據實際情況去配置適合實際需求的虛擬機嗎。
所以沒有最優的jvm配置,只有最適合當前應用場景的jvm,就像乒乓球拍子裏面沒有所謂最好的拍子,只有最適合你手的乒乓球拍。

再舉個例子更粗暴的例子,比如我們家裏都有裝東西的“容器”,你能說哪個最好?最大的就最好嗎?你家裏的容器:馬桶,夠大,你用馬桶泡方便麪不?洗腳盆就比較大,你用洗腳盆洗臉不?
說這麼多廢話,表明自己的一個立場:學jvm自動內存管理這一塊,學的就是分析應用場景,根據實際應用需求、實現方式選擇最適合的收集器組合,各種參數組合。

Java技術體系中所提倡的自動內存管理,其實可以理解爲自動化的解決了兩個問題:
一、給對象分配內存。
二、回收分配給對象的內存(即垃圾回收)。

先說一下給對象分配內存,模糊的說就是在堆上分配空間,對象主要在新生代的Eden區(主要是,並不全部都是,比如有可能在TLAB上分配),也有直接分配到老年區中的情況,具體情況具體分析,分在哪裏還是由採用的垃圾回收器和內存相關的參數設置共同決定的。

對象優先在Eden區域分配
大多情況,對象都在新生代Eden區域中分配。Eden沒有足夠空間了,虛擬機將發起一次Minor GC(Minor GC是發生在新生代的垃圾收集,IBM有一個調研說的好像是Java對象有90%都是朝夕生滅,所以Minor GC是非常頻繁的,,回收速度也是很快的)。
新生代分爲一個Eden區域和兩個Survivor區,但是總的可用空間是eden+一個Survivor區域(這裏涉及到複製算法,在垃圾清理的時候,需要把Eden和一個Survivor存活的對象搬到另一個空的Survivor上)。
-XXSurvivorRatio=10 決定了Eden和一個Survivor的空間比例是10:1。

大對象直接進入老年代
大對象是指需要大量連續內存空間的對象(寫程序的時候應該儘量避免存活時間短的大對象),經常出現大對象容易導致不連續的總內存還夠,但是提前觸發垃圾處理來獲取連續的空間供大對象使用。
參數-XXPertenureSizeThreshold,凡是大於這個參數設定的值的對象直接在老年代分配。目的是避免在Eden區和兩個Survivor區發生大量的複製。

長期存活的對象進入老年代
沒有年齡如何對得起新生代,老年代,這兩個名字?每個對象都有一個Age計數器。如果對象在Eden出生且經過了一次Minor GC後還存活,並且被Survivior容納(有可能存在對象大,存不到Survivior而直接到老年代的情況),這個時候Age就等於1。之後也一樣,每經歷一次Minor GC,Age++,jvm有一個參數-XXMaxTenuringThreshold默認等於15,這個參數當對象年齡達到15,就會移動到老年代中,當然這個參數也是可以修改的。

空間分配擔保
在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果大於,那麼Minor GC可以確保是安全的。如果小於,則虛擬機會查看HandlePromotionFailure設置的值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次移動到老年代對象的平均大小,如果大於,將會嘗試進行一次MInor GC,儘管這次Minor GC是有風險的;如果小於,或者HandlePromotionFailure設置不允許冒險,那這是要進行一次Full GC(Full GC/Major GC:發生在老年代的GC,出現這個GC經常伴隨至少一次Minor GC,但並不絕對。Major GC一般速度會比Minor GC慢10倍以上。)。
關於冒險,新生代用的複製算法,出現大量對象在Minor GC後仍然存活的情況,就需要老年代進行分配擔保,把Servivor放不下的對象直接進入老年代。老年代要進行這樣的擔保,前提條件老年代能容納下這個對象吧?一共多少對象會活下來在實際情況下是無法作出預判的,所以只好取之前每一次回收移動到老年代對象容量的平均值作爲參考,與老年代剩餘空間就進行比較,決定是否進行full gc來讓老年代騰出更多空間。
取平均值學過數學的都知道,只能做一個參考罷了,如果存活的對象徒增,依然會擔保失敗。如果失敗,就只能重新發起一次Full GC。雖然擔保失敗繞的圈子比較大,但是大部分情況下都還是會將HandlePromotionFailure開關打開,避免Full GC過於頻繁。說白了一句話概括:空間分配擔保的開啓,可以降低Full GC發生的頻率。

2018.1.30更 看到了幾張好圖,分享給大家


這裏寫圖片描述

這裏寫圖片描述

這裏寫圖片描述

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