JVM運行時內存組成分爲一些線程私有的,其餘的是線程共享的。java
線程私有
- 程序計數器:當前線程所執行的字節碼的行號指示器。
- Java虛擬機棧:java方法執行的內存模型,每一個方法被執行時都會建立一個棧幀,存儲局部變量表,操做棧,動態連接,方法出口等信息。每一個線程都有本身獨立的棧空間,線程棧只存儲基本類型和對象地址,方法中局部變量存放在線程空間中。
- 本地方法棧:Native方法服務,在hotspot虛擬機中和java虛擬機棧合二爲一。
線程共享
- java堆:存放對象實力,幾乎全部的對象實例及其屬性都在這裏分配內存。此外,jvm在內存新生代eden space中開闢了一塊線程私有的區域,稱做TLAB(Thread Local Allocation Buffer),也是每一個線程的緩衝區,默認設定爲佔用Eden space的1%。在編譯器作逃逸分析的時候,根據分析結果,決定是在棧上仍是在堆上分配內存,若是在堆上則再分析是否在TLAB上分配內存。在TLAB上分配因爲是線程私有的,所以沒有鎖的開銷,效率較高。
- 方法區:存儲已經被虛擬機加載的類信息,常量,靜態變量,JIT編譯後的代碼等數據,也稱做永久代。java7已經把字符串常量池移動到堆中,在調用String的intern方法時,若是堆中存在相同的字符串對象,會直接保存對象的引用,不會從新建立對象。
- 直接內存:NIO,Native函數直接分配的堆外內存。DirectBuffer引用會使用此部份內存。
內存分配過程
- 編譯器經過逃逸分析,肯定對象是在棧上分配仍是堆上分配。若是在堆上分配直接進入步驟4。
- 若是是tlab_top + size <= tlab_end,則在TLAB上直接分配對象並增長tlab_top的值。若是現有TLAB不足存放當前對象則進入步驟3。
- 從新申請一個TLAB,並再次嘗試存放當前對象,若是放不下,則進入步驟4。
- 在Eden區加鎖(此區多線程共享),若是eden_top + size <= eden_end,則將對象存放在eden區,增長eden_top的值,若是eden區不足以存放,則進入步驟5。
- 執行一次YGC。
- 通過YGC後,若是eden還放不下對象,則直接分配到老年代。
對象訪問
- 句柄訪問:經過棧本地變量表,找到堆中對象實例指針,根據指針在堆中找到實例數據,在方法區中找到對象類型數據。
- 直接指針:經過棧本地變量表,找到堆中對象實例指針,對象實例指針中保存對象實例數據,在方法區中找到對象類型數據。
對象建立
- 對象在eden完成內存分配。
- eden滿了,在建立對象,會由於申請不到空間,觸發minor gc,堆eden+s 區進行回收。
- 進行minor gc時,eden區不能被回收的對象進入s區,另外一個s區中不能被gc回收的對象也會進入這個s區,始終保證一個s區空置。
- 若是s區滿了,這些對象會被copy到old區,或者s區沒有滿,可是有些對象足夠old了,會被放入old區。
- old區滿了以後,進行full gc。
內存溢出
在JVM申請內存的過程當中,會遇到沒法申請到足夠內存的狀況,從而致使內存溢出。多線程
- 虛擬機棧和本地方法區棧溢出:statkoverflowerror:線程請求的棧深度大於虛擬機所容許的最大深度,循環遞歸會觸發這種OOM。outfomemoryerror:虛擬機在擴展棧時沒法申請到足夠的內存空間,通常能夠經過不停建立線程觸發這種OOM。
- java堆溢出:建立大量對象而且對象生命週期很長狀況時,會引起outofmemoryerror。
- 方法區溢出:方法區存放class等元數據信息,若是產生大量的類(如CGLIB),會引起這種內存溢出,outofmemoryerror:permgen space,在使用hibernate等動態生成類框架時會引發這種狀況。
垃圾回收和系統吞吐量
- 吞吐量:指的是單位時間內完成的工做量的度量。
- 響應時間:是提交請求和返回該請求的響應之間使用的時間。
一般平均響應時間越短,系統吞吐量越大,平均響應時間越長,吞吐量越小。併發
- 並行垃圾回收器關注的是吞吐量,會在必定程度上犧牲響應時間。可能某次請求會特別慢。
- 併發垃圾回收器關注的是請求響應時間,會犧牲吞吐量。會盡可能使得每次請求時間維持在差很少水平。
對於CMS觸發full gc的狀況:框架
- old區使用到必定比例時觸發,經過cmsinitiatingoccupancyfaction來設置。