分爲4個方面來介紹內存分配與回收,分別是內存是如何分配的、哪些內存須要回收、在什麼狀況下執行回收、如何監控和優化GC機制。java
java GC(Garbage Collction)垃圾回收機制,是java與C/C++的主要區別之一。經過對jvm中內存進行標記,自主回收一些無用的內存。目前使用的最多的是sun公司jdk中的HotSpot,因此本文也以該jvm做爲介紹的根本。算法
1.Java內存區域多線程
在java運行時的數據取裏,由jvm管理的內存區域分爲多個部分:jvm
程序計數器(program counter register):程序計數器是一個比較校的內存單元,用來表示當前程序運行哪裏的一個指示器。因爲每一個線程都由本身的執行順序,因此程序計數器是線程私有的,每一個線程都要由一個本身的程序計數器來指示本身(線程)下一步要執行哪條指令。優化
若是程序執行的是一個java方法,那麼計數器記錄的是正在執行的虛擬機字節碼指令地址;若是正在執行的是一個本地方法(native方法),那麼計數器的值爲Undefined。因爲程序計數器記錄的只是當前指令地址,因此不存在內存泄漏的狀況,也是jvm內存區域中惟一一個沒有OOME(out of memory error)定義的區域。spa
虛擬機棧(JVM stack):當線程的每一個方法在執行的時候都會建立一個棧幀(Stack Frame)用來存儲方法中的局部變量、方法出口等,同時會將這個棧幀放入JVM棧中,方法調用完成時,這個棧幀出棧。每一個線程都要一個本身的虛擬機棧來保存本身的方法調用時候的數據,所以虛擬機棧也是線程私有的。線程
虛擬機棧中定義了兩種異常,若是線程調用的棧深度大於虛擬機容許的最大深度,拋出StackOverFlowError,不過虛擬機基本上都容許動態擴展虛擬機棧的大小。這樣的話線程能夠一直申請棧,直到內存不足的時候,會拋出OOME(out of memory error)內存溢出。指針
本地方法棧(Native Method Stack):本地方法棧與虛擬機棧相似,只是本地方法棧存放的棧幀是在native方法調用的時候產生的。有的虛擬機中會將本地方法棧和虛擬棧放在一塊兒,所以本地方法棧也是線程私有的。對象
堆(Heap):堆是java GC機制中最重要的區域。堆是爲了放置「對象的實例」,對象都是在堆區上分配內存的,堆在邏輯上連續,在物理上不必定連續。全部的線程共用一個堆,堆的大小是可擴展的,若是在執行GC以後,仍沒有足夠的內存能夠分配且堆大小不可再擴展,將會拋出OOME。blog
方法區(Method Area):又叫靜態區,用於存儲類的信息、常量池等,邏輯上是堆的一部分,是各個線程共享的區域,爲了與堆區分,又叫非堆。在永久代還存在時,方法區被用做永久代。方法區能夠選擇是否開啓垃圾回收。jvm內存不足時會拋出OOME。
直接內存(Direct Memory):直接內存指的是非jvm管理的內存,是機器剩餘的內存。用基於通道(Channel)和緩衝區(Buffer)的方式來進行內存分配,用存儲在JVM中的DirectByteBuffer來引用,當機器自己內存不足時,也會拋出OOME。
舉例說明:Object obj = new Object();
obj表示一個本地引用,存儲在jvm棧的本地變量表中,new Object()做爲一個對象放在堆中,Object類的類型信息(接口,方法,對象類型等)放在堆中,而這些類型信息的地址放在方法區中。
這裏須要知道如何經過引用訪問到具體對象,也就是經過obj引用如何找到new出來的這個Object()對象,主要有兩種方法,經過句柄和經過直接指針訪問。
經過句柄:
在java堆中會專門有一塊區域被劃分爲句柄池,一個引用的背後是一個對象實例數據(java堆中)的指針和對象類型信息(方法區中)的指針,而這兩個指針都是在java堆上的。這種方法是優點是較爲穩定,可是速度不是很快。
經過直接指針:
一個引用背後是一個對象的實例數據,這個實例數據裏面包含了「到對象類型信息的指針」。這種方式的優點是速度快,在HotSpot中用的就是這種方式。
2.內存是如何分配和回收的
內存分配主要是在堆上的分配,如前面new出來的對象,放在堆上,可是現代技術也支持在棧上分配,較爲少見,本文不考慮。分配內存與回收內存的標準是八個字:分代分配,分代回收。那麼這個代是什麼呢?
jvm中將對象根據存活的時間劃分爲三代:年輕代(Young Generation)、年老代(Old Generation)和永久代(Permannent Generation)。在jdk1.8中已經再也不使用永久代,所以這裏再也不介紹。
年輕代:又叫新生代,全部新生成的對象都是先放在年輕代。年輕代分三個區,一個Eden區,兩個Survivor區,一個叫From,一個叫To(這個名字是動態變化的)。當Eden中滿時,執行Minor GC將消亡的對象清理掉,仍存活的對象將被複制到Survivor中的From區,清空Eden。當這個From區滿的時候,仍存活的對象將被複制到To區,清空From區,而且原From區變爲To區,原To區變爲From區,這樣的目的是保證To區一直爲空。當From區滿無對象可清理或者From-To區交換的次數超過設定(HotSpot默認爲15,經過-XX:MaxTenuringThreashold控制)的時候,仍存活的對象進入老年代。年輕代中Eden和Servivor的比例經過-XX:SerivorRation參數來配置,默認爲8,也就時說Eden:From:To=8:1:1。年輕代的回收方式叫作Minor GC,又叫中止-複製清理法。這種方法在回收的時候,須要暫停其餘全部線程的執行,致使效率很低,如今雖然有優化,可是僅僅是將中止的時間變短,並無完全取消這個中止。
年老代:年老代的空間較大,當年老代內存不足時,將執行Major GC也叫Full GC。若是對象比較大,可能會直接分配到老年代上而不通過年輕代。用-XX:pertenureSizeThreashold來設定這個值,大於這個的對象會直接分配到老年代上。
3.垃圾收集器
在GC機制中,起做用的是垃圾收集器。HotSpot1.6中使用的垃圾收集器以下(有連線表示有聯繫):
Serial收集器:新生代(年輕代)收集器,使用中止-複製算法,使用一個線程進行GC,其餘工做線程暫停。
ParNew收起:新生代收集器,使用中止-複製算法,Serial收集器的多線程版,用多個線程進行GC,其餘工做線程暫停,關注縮短垃圾收集時間。
Parallel Scavenge收集器:新生代收集器,使用中止-複製算法,關注CPU吞吐量,即運行用戶代碼的時間/總時間。
Serial Old收集器:年老代收集器,單線程收集器,使用標記-整理算法(整理的方法包括sweep清理和compact壓縮,標記-清理是先標記須要回收的對象,在標記完成後統一清楚標記的對象,這樣清理以後空閒的內存是不連續的;標記-壓縮是先標記須要回收的對象,把存活的對象都向一端移動,而後直接清理掉端邊界之外的內存,這樣清理以後空閒的內存是連續的)。
Parallel Old收集器:老年代收集器,多線程收集器,使用標記-整理算法(整理的方法包括summary彙總和compact壓縮,標記-壓縮與Serial Old同樣,標記-匯老是將倖存的對象複製到預先準備好的區域,再清理以前的對象)。
CMS(Concurrent Mark Sweep)收集器:老年老代收集器,多線程收集器,關注最短回收時間停頓,使用標記-清除算法,用戶線程能夠和GC線程同時工做。
G1收集器:JDK1.7中發佈,使用較少,不做介紹。
Java GC是一個很是複雜的機制,想要詳細說清楚他須要不少時間,若有錯誤懇請指正。