您好,登錄后才能下訂單哦!
這篇文章主要講解了“JVM中垃圾回收的判定標準和內存相關參數介紹”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“JVM中垃圾回收的判定標準和內存相關參數介紹”吧!
最終目的是將內存中無用的對象回收掉。具體的判定方法有:
引用計數法,不采用,指的是維護對象被引用的次數,次數為0則意味著是垃圾。
可達性算法-GC Roots tracing,指的是從GC Roots開始往下遍歷所有引用的對象,(每個GC Root就是一個樹狀圖),所有被引用到的對象就是需要存活的對象,其他對象可以被回收。GC Root指的是,虛擬機棧(棧幀中的本地變量表)中引用的對象,方法區中非基本類型的類靜態變量(一個地址)所引用的對象,本地方法棧中JNI(即一般說的Native方法)引用的對象。
-Xms Java堆內存初始大小
-Xmx Java堆內存最大大小
-Xmn Java堆內存中的新生代大小,扣除它就是老年代大小
-XX:PermSize(1.8之后:-XX:MetaspaceSize) 永久代初始大小
-XX:MaxPerSize(1.8之后:-XX:MaxMetaspaceSize) 永久代最大大小
-Xss 每個線程的棧內存大小
注:通常情況下,Xms和Xmx,-XX:PermSize和-XX:MaxPerSize都會設置為一樣。
-XX:MaxTenuringThreshold 多少歲進入老年代-默認15
-XX:PretenureSizeThreshold 超過多少字節的大對象直接進入老年代
-XX:HandlePromotionFailure MinorGC時,如果老年代剩余空間小于新生代對象總大小,但是如果大于之前平均進入老年代對象的大小,是否嘗試進行MinorGC(默認開啟)
-XX:SurvivorRatio=8 Eden區的比例
上面看不懂的參數不要深究,等下提到回過頭再來看,這里只是將所有參數羅列出來方便查找。
JVM中,將對象在內存中分為了三代:
年輕代:很快被回收的對象,存在于堆,具體還在內存中分為了1個eden區,和2個survivor區。
老年代:長期存在的對象,存在于堆
永久代:指的就是方法區(存放Class元數據),回收條件較苛刻,需滿足:該類所有實例對象所有已經從堆內存被回收,該類classLoader已經被回收,該類Class對象沒有任何引用
為什么要分代勒,因為針對每個年齡代,都有不同的垃圾回收算法,以及內存分配機制。如果將所有對象放在一起,第一是會造成頻繁遍歷判斷回收的開銷,第二是會造成復制、移動的開銷,為什么會有復制、移動,因為回收內存必然會造成內存碎片,而內存碎片會導致空間浪費,所以必須通過復制、移動來清理隨便,使得空閑內存連續。
如上圖,至于年輕代為什么要如此分配,與特定的回收算法有關。
大部分對象剛創建的時候都會分配在年輕代的Eden區,只要年輕代空間不夠就會觸發MinorGC(只回收年輕代內存),minorGC采取復制算法進行回收,當JVM運行觸發第一輪minorGC時,會將eden區存活的對象先復制到一個suprivor區。然后刪除eden區對象,當觸發下一輪minorGC時,又把suprivor區和eden區的存活的對象轉移到另一個suprivor區,然后刪除這兩個區的所有對象。依次類推。至于為什么要用復制算法,包括老年代的標記整理算法,這是考慮到了避免內存碎片。如果對象內存不連續,會造成很多的空間浪費。
老年代的對象都是從年輕代根據一定的規則流轉過來的。 具體有幾類流轉方式:
超過指定年齡(參數-XX:MaxTenuringThreshold 配置,默認15),這里年齡指的是沒有被垃圾回收,存活下來一次理解為增加一歲。流轉到老年代。
大對象直接進入,超過參數指定字節數(-XX:PretenureSizeThreshold)設置的字節數的大對象會直接進入老年代,這是因為對象越大,復制開銷就越大。
動態年齡判斷規則進入,意思是不一定要到指定年齡再流轉到15,如果某一年齡以上的對象到達一定大小,也會提前進入老年代。當躲過一輪GC的對象加起來超過surrvivor區50%,如年齡1+年齡2+年齡n一直累加,直到年齡n的時候發現加起來超過了surrvivor空間的50%,則年齡n以上的對象直接進入老年代
minorGC發生時,suprivor區放不下,則所有存活對象轉移到老年代。這里涉及一個老年代分配擔保規則,指的是每次MinorGC發生時,都會判斷老年代可用內存大不大于,年輕代存活對象內存之和,如果大于則直接進行minorGC,如果小于則要看參數XX:HandlePromotionFailure是否啟用(默認啟用),如果啟用則對老年代這次需要承載的轉移對象內存進行預估(取前面minorGC被轉移的平均內存大小),若大于則也進行MinorGC,若意料狀況外轉移內存超出了老年代可用空間,則進行FullGC,若fullGC還是不夠,則拋出OOM錯誤。FullGC是采取的標記整理算法,指的是移動存活對象,讓內存連續,然后刪除需要回收的對象,為什么使用標記整理?因為認為老年代對象存活幾率高,復制算法不劃算。
永久代存放的是元數據信息,當類加載時,類元數據信息寫入永久代,fullGC時永久代數據被回收,回收條件是:該類所有實例對象所有已經從堆內存被回收,該類classLoader已經被回收,該類Class對象沒有任何引用。
附圖:
現一個日處理量上億數據的計算系統,不斷從Mysql和其他數據中間件中提取數據進行計算處理。每分鐘執行500次數據提取和計算任務,每次任務處理耗時10秒,每次處理1萬條數據(每條數據20個字段),但是集群部署,共5臺機器,1臺機器每分鐘處理100次任務,每臺機器是4核8G內配置,JVM分了4G,3G堆內存,1.5G年輕代,1.5G老年代。
我們先來估算一下內存占用:
每條數據20個字段,可以估算一條數據為1KB大小左右
每次計算1W條,那么一個任務占用內存就是1KB*1W=10MB數據左右,一臺機器每分鐘處理100次任務,暫用內存約為1G左右,基本上一分鐘多點后Eden區就被占滿了。
實際生產環境是怎么樣的勒?
一分鐘之后的第一次GC,此時的內存情況:
每個任務處理10S,意味著還有大概六分之一的數據應該存活,算200M,但是200M放不進Survivor區,所以會嘗試往老年代放,老年代現在大于1.2G所以直接放就是了。
每次MinorGC都會有大概200M進入老年代,當進行到第三次時
此時老年代可用容量小于年輕代對象總內存,默認判斷老年代剩余空間是否大于平均每次MinorGC轉移過來的老年代對象容量,這里是大于,所以還是繼續MInorGC。
當進行到第八次時
此時會觸發FullGC清理老年代,于是老年代的對象被全部清理掉了:
然后繼續回到原來的第一次,每8次進行一次FullGC,也就是8分鐘進行一次
重新調整新生代老年代比例,擴大新生代內存為2GB,老年代1GB
此時一個Survivor區有200MB,每次MinorGC后都能存放的下存活對象,不用往老年代轉移(當然還是有轉移,這只是避免了suprivor區過小被迫轉移的對象)。JVM優化的策略最核心的就是減少FullGC次數,因為掃描對象多了一個老年代和永久代、永久代標記算法略微復雜、老年代整理時由于對象較多比較慢的原因,FullGC效率是遠遠低于MinorGC的,一般時間是minorGC的10倍以上。
感謝各位的閱讀,以上就是“JVM中垃圾回收的判定標準和內存相關參數介紹”的內容了,經過本文的學習后,相信大家對JVM中垃圾回收的判定標準和內存相關參數介紹這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。