Java的內存溢出(OOM)

JAVA內存區域中不同的結構會由於不同的原因而導致內存溢出。JAVA內存主要分爲堆,棧,方法區和程序計數器四個部分。程序計數器是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。其他三個區域都有可能發生內存溢出。下面我們來具體說說。

對於內存溢出(Memory Overflow),還有一個相似的概念就是內存泄露(Memory Leak)。它們有着本質的不同,內存泄露會導致內存溢出。內存泄露是沒有必要存活的對象應該被GC回收,然而還有引用指向對象導致GC不能回收,這一般是程序的問題。

一 堆溢出

      堆用來存儲對象實例,只要不斷的創建對象,並且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,則在對象數量到達最大堆的容量限制後會產生內存溢出異常。內存溢出在堆中就會導致OOM。下面的例子在需要設定虛擬機的參數:-Xmx(堆的最大值)和 -Xms(堆的初始值)。下圖顯示異常位置:Java heap space

二 棧溢出

 棧溢出有兩種異常:

1.如果線程請求的棧深度大於虛擬機所允許的最大深度,則將拋出StackOverflowError異常。

2.如果虛擬機在擴展棧時無法申請到做夠的內存空間,就會拋出OutOfMemoryError異常。

上面兩種情況存在着相互重疊,本質是對同一件事情的兩種描述而已。可以創建很多個棧幀來讓它溢出,其實就是定義一個遞歸方法,讓棧幀佔滿棧空間,這會導致StackOverflowError異常,棧深度一般在1000到2000。

創建很多個線程可以使虛擬機拋出OutOfMemoryError,由於Java線程是映射到操作系統的內核上,因此在做這個實驗時很容易導致死機,我試驗過一次,電腦就卡死了。下面就展示拋出的第一個異常:


三 方法區溢出
在JDK1.6之前,常量池是在方法區中的,則可以通過不斷的創建常量使得方法區(永久代/持久代)溢出。不過在Java7,常量池被移到了堆中,這時在JRE7中就不會因爲常量池而導致方法取溢出。同時方法區又是存放Class的相關信息,動態生成很多類會也導致方法區溢出。
在Java6及之前的版本,可以通過參數-XX:PermSize=10M -XX:MaxPermSize=10M 限制方法區的大小,從而間接限制常量池的大小。例子如下圖:

四 本機直接內存溢出
直接內存通過在系統中直接分配內存得到,使用NIO包可以獲得直接內存。下面的例子產生了OOM錯誤,可以發現拋出的異常沒有具體指向哪個空間:


參考:《深入理解Java虛擬機》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章