Java記憶體管理和記憶體回收

來源:互聯網
上載者:User

標籤:

筆記,深入理解java虛擬機器

 

Java運行時記憶體地區

程式計數器,線程獨佔,當前線程所執行的位元組碼的行號指標,每個線程需要記錄下執行到哪兒了,下次調度的時候可以繼續執行,這個區是唯一不會發生oom的

,線程獨佔,包含虛擬機器棧或native method stack,用於存放局部變數的

,線程共用,用於分布對象執行個體的,後面說的記憶體管理和記憶體回收基本都是針對堆的

方法區,線程共用,用於存放被虛擬機器載入的類,常量,靜態變數; Java虛擬機器規範,把方法區描述為堆的邏輯部分,所以也被稱為“永久代”,在大量使用反射,動態代理,ClassLoader的情境下,要考慮永久代的回收

對於一個簡單的,對象產生

Object obj = new Object();

會涉及到3個區,

圖中描述兩種對象訪問方式,有控制代碼,可以屏蔽對象實際地址的改變(gc時對象地址經常會改變),不用控制代碼效率更高

 

記憶體回收 垃圾判定

對於如何判定對象是垃圾,教科書的答案是引用計數,並且也在COM,python中得到較好的應用

引用計數的問題是,難以解決循環參考的問題,
A.instance = B; B.instance =A
這樣會導致A,B的引用計數都不為0

所以對於Java,C#,Lisp都是採用GC Roots Tracing的方式,
原理也很簡單,就是選取一系列GC Roots對象,只保留從root對象可達的對象,其餘都是垃圾

Java中的GC roots包含,

  • JavaStack中的引用的對象
  • 方法區中靜態引用指向的對象
  • 方法區中常量引用指向的對象
  • Native方法中JNI引用的對象

 

記憶體回收演算法

Mark-Sweep,最原始的想法,先標記出所有垃圾對象,然後回收掉;問題是,效率低,而且會產生大量記憶體片段

Copying,最簡單的copying,把記憶體分兩半,先用一半,然後回收時,把有效對象copy到另外一半

這個首先只適用於年輕代,即垃圾對象占較高比例的case
再者,空間太浪費了,只能用一半

所以,現在實際使用的版本為,分為一個較大的eden區,兩個較小的survivor區
比例一般為8:1:1,其中一個survivor區是空的,這樣只浪費10%的空間
能這樣做的前提就是,每次只有最多10%的對象存活
對象先放到eden區,eden區滿了,進行minor gc,把eden區和有資料的survivor區的存活對象,放到另一個空的survivor區中
兩個survivor區,雖然命名為from和to,但是其實沒有任何區別,完全對等的

當如果survivor區的空間不夠,就需要放到年老代(稱為handle promotion,即年老代要為survivor提供擔保,你那空間不夠,可以放我這,類似貸款擔保),如果年老代的空間也不夠或不能接受Handle promotion失敗,就需要full gc去回收年老代

Mark-Compact,前面說copying只適用於存活對象比例較低的case,所以適合年輕代,但對於年老代這樣的,用copying肯定是不行的;
Mark-compact,mark還是一樣的,只是在回收時,會把對象做平移,消除片段

 

垃圾收集器

Serial收集器

最簡單的,單線程,收集時會stop the world,所有使用者線程暫停
可以用於收集新生代或老年代
收集新生代,用copying演算法
收集老年代,用標記-整理,稱為serial old

簡單高效,適用於單CPU環境;是虛擬機器Client模式的預設收集器
缺點,會stop the world

ParNew收集器

只是serial的多線程版本,適用於多核環境,如果在單核的機器上,效率還不如serial
優勢是,可以配合CMS收集器使用,因為CMS作為老年代的收集器,只能配合Serial或ParNew作為新生代的收集器

CMS(Concurrent Mark Sweep)收集器

以最短停頓時間為目標的收集器,適合用於網站或B/S系統的服務端,重視服務響應速度的情境。
該收集器相對比較複雜,整個過程分為,
1. 初始標記(CMS initial mark),stop the world,標記GC Roots直接關聯到的對象,速度很快
2. 並發標記(CMS concurrenr mark), 並行進行GC Roots Tracing的過程
3. 重新標記(CMS remark),stop the world,由於並發標記和使用者線程是並發執行的,所以需要對標記進行最後的修正,消耗時間會大大小於並發標記時間
4. 並發清除(CMS concurrent sweep),最後進行sweep

 

缺點,
a. CPU敏感,頻繁GC會導致CPU卡死
b. 無法處理浮動垃圾(floating garbage),在並發清理階段產生的新垃圾無法在這次完成回收
c. 需要預留較大的記憶體,由於CMS收集過程是和使用者應用並發進行的,所以不能等到老年代快被佔滿再做,需要提前進行收集,預設是設為68%,這是比較保守的設定,可以減少以降低gc的次數;但是如果在CMS收集過程中,出現使用者應用程式記憶體不夠的情況,會發生”Concurrent Mode Failure”,這樣虛擬機器只能用後備方案,serial old收集器進行年老代的收集(因為應用已經無法並發執行),這樣應用停頓時間就會很長,所以需要設定合理的比例
d. 因為採用sweep,會有大量片段

Parallel Scavenge收集器

新生代收集器,設計目的是達到可控制的throughput,CPU運行使用者代碼時間/CPU總消耗時間,說白了,就是保證CPU更多的執行使用者代碼而非gc
適合後台運算,不太需要互動的情境

但魚和熊掌不可兼得,throughput和GC停頓時間是需要tradeoff的
降低新生代的空間大小,縮小gc的間隔,都可以減少GC的停頓時間,但也會降低throughput

並且該收集器,支援UseAdaptiveSizePolicy參數, 這樣不需要使用者指定新生代大小,Eden和Survivor比例,年老代晉陞年齡等,虛擬機器會根據運行效能監控去最佳化調整

Parallel Old收集器

前面提到的parallel sacvenge收集器是無法與CMS收集器配合使用的,所以之前只能配合Serial Old來收集老年代,導致效率低
Parallel Old作為Serial Old的多線程版本,可以更好的和parallel sacvenge收集器配合,真正達到throughput優先的收集

G1(Garbage First)收集器

新一代的收集器,不同於之前的收集器,會收集整個新生代或老年代
G1會把整個Java堆劃分為多個固定大小的地區,並跟蹤垃圾堆積程度,並每次收集垃圾最多的地區,可以大大提高收集效率

JVM收集器對應參數

 

記憶體配置和回收策略

對象往往在新生代的Eden區分配,Eden區空間不夠,發起minor gc,會將eden和一個survivor區的存活對象copy到另一個survivor區,如果另一個survivor區空間不夠,存入老年代

大對象會直接進入老年代,比如很長的字串或很大的數組,大對象對於JVM記憶體配置是個壞訊息,因為大對象需要找到連續記憶體,否則會觸發gc,所以短命的大對象是需要盡量避免的

長期存活的對象進入老年代,對象在新生代每經曆一次minor gc,年齡加1, 預設達到15歲會進入老年代

每次Minor GC時,虛擬機器會檢測每次晉陞到老年代的平均大小是否大於老年代當前剩餘大小,如果小於,則進行full gc

Java記憶體管理和記憶體回收

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.