1. 程式人生 > >[jvm解析系列][一]Java記憶體區域分配和記憶體溢位異常OOM

[jvm解析系列][一]Java記憶體區域分配和記憶體溢位異常OOM

學過作業系統的同學應該比較清楚,一個作業系統必須要有完善的記憶體管理系統(頁/段式的管理),相應的jvm全稱java虛擬機器應該也有類似的一種管理記憶體的方式,這種方式是建立在真實的作業系統記憶體管理方式之上的,他把記憶體分配成了不同的區域,形成了java記憶體模型。

那麼,對於其他部落格講解這種題目要先拋一個圖解出來,我並不想這樣。因為這種模型的出現肯定是要解決問題的,我們需要順延著前人設計jvm記憶體模型的腳步,看看到底為什麼要這樣設計,才能更好的理解它。

首先,我們思考一下,java需要什麼區域呢?

1、必不可少的,一個程式計數器,用來記錄當前程式執行的位元組碼指令,jvm使用了棧而不是暫存器結構(往後看相信你會理解的)。對於執行緒來講,每個執行緒肯定是需要記錄自己的執行位置,所以程式計數器是執行緒私有的

2、既然有了程式計數器我們還需要一個區域來存放當前執行的程式,那就是Java虛擬機器棧,每一個方法的執行就會新增一個棧幀在虛擬機器棧裡(如果想要詳細的瞭解棧幀請關注部落格,隨著系列的加深,我會一個個講解)。對於執行緒來講虛擬機器棧同樣的是執行緒私有。

3、一樣的常用jvm的都知道還有一種jni的呼叫,為了這種方法的執行,設計了一種跟java虛擬機器棧的雙胞胎區域叫本地方法棧,除了執行native方法外其他的幾乎一致。(在有的虛擬機器裡不區分native和java)

4、大家都知道java是面向物件的,既然方法都出來了,那麼類儲存在那呢?沒錯,jvm也分化出來了這樣的一個區域叫做方法區(也稱為永久代,因為這個區域很長時間不會發生gc)。這個區域裡儲存了虛擬機器家在的所有的類資訊,常量和靜態變數等。(內含一個執行時常量池

主要用於存放編譯器生成的字面量和符號引用)他是執行緒公有的。

5、如果類都有了,那麼物件呢?沒錯這就是jvm的重頭戲,很多工作都在這個地方完成,他就是傳說中的java堆,在有的地方甚至把jvm籠統的分為堆疊,可見java堆的重要性。java堆裡面存放了幾乎所有的物件例項,在java虛擬機器規範中這樣說:所有的物件例項和陣列都要在堆上分配。隨著JIT編譯器等的發展也不那麼絕對了。(java堆仍然很複雜,後期單開一篇部落格詳解)。它肯定是執行緒公有的。

6、到這裡,好像java所有的需要都被分配了,以前確實是的,但是在JDK1.4之後,java引入了nio技術(不在本章範圍內講解),這就要求需要分配一個新的區域,沒錯,他叫直接記憶體

。值得一提的是,這一區域並不是jvm的規範中的一部分,也不是虛擬機器執行時資料區的一部分,但是他會引起OOM。

那麼至此所有的記憶體區域分配完畢,配圖如下幫助理解,相信你已經差不多的瞭解和理解了jvm分配的方式和目的了。

記憶體區域分配配圖


上節說到~jvm的記憶體分配,所以對應的應該有6種溢位方式,實際上程式計數器一般不會溢位,我又把兩個方法棧合在一起講,所以溢位的可能性有4種

嘿嘿嘿嘿,我要去考試了,等我回來再補吧~

回來了繼續補

1、Java堆溢位

java堆溢位一般都是因為在for或者遞迴裡呼叫了太多次new物件的操作,並且可以被GCROOT查詢到不能GC,而導致java堆記憶體不足報錯,報錯一般如下:

OutOfMemoryError:Java heap spce

2、虛擬機器棧和本地方法棧溢位

虛擬機器棧和本地方法溢位一般也是陷入了無限迴圈裡並且在無限迴圈裡一直在呼叫一個方法,因為一般情況下棧深能有1000~2000,報錯一般都是StackOverflowError,OOM反而不常見,在處理這種異常時很棘手,需要減少其他區域的記憶體,因為這塊記憶體是用總記憶體減去堆和方法區得到的。報錯如下:

StackOverflowError.

3、方法區和執行時常量池溢位

這個區域想要溢位單單使用建立類的方法和常量是困難的,所以可以使用String.intern()方法直接將字串加入到常量池。報錯一般如下

OOM:PermGen space

4、本機直接記憶體溢位

由於上文知道直接記憶體一般用於NIO操作,所以使用了native方法而且因為這個區域的特殊性Heap dump並沒有明顯的報錯。錯誤資訊如下:

OutOfMemory。