<a href="#%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3jvm%e7%ac%94%e8%ae%b0%e4%b9%8b%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86%e6%9c%ba%e5%88%b6">深入了解jvm筆記之記憶體管理機制</a>
<a href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e6%95%b0%e6%8d%ae%e5%8c%ba%e5%9f%9f">運作時資料區域</a>
<a href="#%e7%a8%8b%e5%ba%8f%e8%ae%a1%e6%95%b0%e5%99%a8">程式計數器</a>
<a href="#jvm%e6%a0%88">jvm棧</a>
<a href="#%e6%9c%ac%e5%9c%b0%e6%96%b9%e6%b3%95%e6%a0%88">本地方法棧</a>
<a href="#java%e5%a0%86">java堆</a>
<a href="#%e6%96%b9%e6%b3%95%e5%8c%ba">方法區</a>
<a href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e5%b8%b8%e9%87%8f%e6%b1%a0">運作時常量池</a>
<a href="#%e7%9b%b4%e6%8e%a5%e5%86%85%e5%ad%98">直接記憶體</a>
<a href="#%e5%af%b9%e8%b1%a1%e8%ae%bf%e9%97%ae">對象通路</a>
<a href="#outofmemoryerror%e5%bc%82%e5%b8%b8">outofmemoryerror異常</a>
<a href="#java%e5%a0%86%e6%ba%a2%e5%87%ba%e7%a4%ba%e4%be%8b">java堆溢出示例</a>
<a href="#jvm%e6%a0%88%e5%92%8c%e6%9c%ac%e5%9c%b0%e6%96%b9%e6%b3%95%e6%a0%88%e6%ba%a2%e5%87%ba">jvm棧和本地方法棧溢出</a>
<a href="#%e8%bf%90%e8%a1%8c%e6%97%b6%e5%b8%b8%e9%87%8f%e6%b1%a0%e6%ba%a2%e5%87%ba">運作時常量池溢出</a>
<a href="#%e6%9c%ac%e6%9c%ba%e7%9b%b4%e6%8e%a5%e5%86%85%e5%ad%98%e6%ba%a2%e5%87%ba">本機直接記憶體溢出</a>
每個線程都有一個程式計數器(pc),是目前線程所執行的位元組碼的行号訓示器,通過改變程式計數器的值來選取下一條指令。各線程之間的計數器互不影響,是線程私有的記憶體。
如果線程執行的是一個java方法,則計數器記錄的為正在執行的位元組碼指令的位址,如果執行的是natvie方法,這計數器的值為空(undifined)。
程式電腦所在的記憶體區域是唯一一個jvm規範中沒有規定oomerror的記憶體區域
jvm棧也是每個線程所私有的記憶體區域,随着線程的建立而配置設定,線程的消亡而回收。jvm棧是描述java方法執行的記憶體模型,每個java方法在執行的時候,都會建立一個棧幀(stack frame),其作用是用來儲存java方法中的局部變量、操作棧、方法出口等資訊。每一個java方法從被調用到執行完畢的過程,都伴随着對應的棧幀在jvm棧中的進棧和入棧。
jvm規範中,該區域中有兩個異常狀況:
-stackoverflowerror:線程請求的棧深度超過了jvm棧所允許的棧深度
-outofmemoryerror:如果jvm棧是動态可擴充的,在無法申請到足夠記憶體時,抛出該異常
本地方棧(native method stack)是描述虛拟機所用到的本地方法的記憶體模型,與jvm棧類似,也會抛出stackoverflowerror和outofmemoryerror
java堆是java虛拟機所管理的最大的記憶體區域,我們所說的垃圾回收,就是對該區域的記憶體進行回收,是以也叫gc堆。java堆是所有線程所公用的記憶體區域,程式中的對象、數組都存放在該區域中。
從記憶體回收的角度來看,由于現在的收集器所采用的都是分代收集算法,是以java堆可以在進一步細分為:新生代和老年代,在新生代中,又可以劃分為eden、from survivor、to survivor等空間。從記憶體配置設定的角度來看,java堆可能劃分為多個線程私有的配置設定緩存區。
java堆可以是實體上不連續但邏輯上要連續的記憶體空間,可以通過指定-xmx(最大)、-xms(最小)、-xmn(新生代)等vm參數來設定java堆得大小。若有新對象申請記憶體空間但是java堆沒有足夠的記憶體配置設定時,會報oomerror。
方法區(method area)也是各線程共享的記憶體區域,用于存儲被虛拟機加載的類資訊、常量、靜态常量、編譯後的代碼等資料看,其還有一個别名叫non-heap(非堆),與java堆加以區分。在hotspot虛拟機上,也可叫做“永久代”(permanent generation),本質上不等價(hotspot将gc收集擴充到了該區域)。
若要對該區域進行gc,主要是回收常量池以及對類型的解除安裝。該區域在記憶體滿後也會抛出oomerror異常。
運作時常量池(runtime constant pool)是方法區内的一部分,class檔案中除了類的版本、字段、方法、接口等描述資訊,還常量池表,用于存放編譯期間生成的葛總常量和符号引,這部分内容在類加載後存放運作時常量池這種(class如何加載會在後面章節提及)。運作時常量池具備動态性,java語言不單單在編譯期間會産生常量,在運作期間也可能将新的常量放入運作時常量池中,如開發人員用了string.intern()方法,之後的異常測試就是用該方法引起常量池抛出oomerror。
直接記憶體(direct memory)不是虛拟機運作時資料區域的一部分,也不是jvm規範中定義的記憶體區域,但其也頻繁被使用。自jdk1.4後,引入了基于通道(channel)與緩沖區的i/o方式,它可以使用native函數庫直接配置設定堆外記憶體,并通過java堆中的directbytebuffer對象操作該塊記憶體。避免了java堆和native堆之間的資料複制,提高了性能。
本機直接記憶體不受java堆大小限制,但是受本機總記憶體大小和處理器的尋址空間限制。在動态擴充時也會抛出oomerror異常。
之前對虛拟機運作時資料區域進行了描述,現在通過執行個體來驗證oom異常發生的情況,可以進一步了解jvm各記憶體區域的存儲内容,及發生異常的情況。
測試的java版本為:
java version “1.7.0_80” java(tm) se runtime environment (build 1.7.0_80-b15) java hotspot(tm) 64-bit server vm (build 24.80-b11, mixed mode)
之前已經提到,對象執行個體都存儲在java堆中,是以可以不斷的建立對象且保證對象不被gc就可以讓java堆溢出
編寫如下代碼清單:
并配置運作時的vm參數:

這裡-xx:useparnewgc是指定用parnew收集器,預設的是
參數解釋: -xms:20m:java堆最小20m
-xmx:20m:java堆最大20m(避免動态擴充)
-xx:+heapdumponoutofmemoryerror:開啟該選項,可以讓虛拟機在抛出outofmemoryerror時dump出目前記憶體堆轉存快照
運作後,就會有如下輸出:
之前有提過,hotspot虛拟機并沒有區分jvm和本地方法棧。在hotspot虛拟機中可以通過制定-xss參數來指定棧的大小。
編寫如下測試代碼:
運作後就會報stackoverflowerror異常:
string.intern()方法可以向運作時常量池中添加内,是以要達到運作時常量池溢出,執行用本地方法intern()像池中不斷添加字元串即可,可以同過-xx:permsize -xx:maxpermsize限制方法區大小。
本機直接記憶體可以通過 -xx:maxdirectmemorysize指定記憶體大小,若不指定,則預設與java堆得最大值一樣。