什麼情況下發生OutOfMemoryError異常?— JVM系列(五)

前言

JVM中除了程序計數器外,其他區域都會發生OutOfMemoryError異常。

那麼當發生時,我們如何判斷是哪個區域發生的?

發生異常時,我們如何進行排查和處理呢?

一、Java堆的溢出

1、設置堆大小

我們可以通過如下參數來設置Java堆的內存情況:

  • -Xms:堆的最小的初始化內存空間。
  • -Xmx:堆的最大可允許分配內存空間。
  • -XX:+HeapDumpOnOutOfMemoryError:設置了此參數,當Java虛擬機內存溢出時,會將當時JVM內堆轉儲快照,以二進制文件的形式dump出來。

2、發生堆溢出

當發生堆的內存溢出時,會在異常信息後面跟隨Java heap space的提示。

打印的堆快照的文件後綴是hprof

3、分析dump文件

然後使用Jdk中自帶的jvisualvm工具,將快照裝載進來,進行分析。

我們需要分清楚內存泄漏與內存溢出的區別:

  • 內存泄漏:可進一步通過工具查看泄漏對象到GC Roots的引用鏈。於是就能找到泄漏對象是通過怎樣的路徑與GC Roots相關聯並導致垃圾收集器無法自動回收的。
  • 內存溢出:就是內存中的對象確實都還必須存活着,那就應當檢查虛擬機堆的參數-Xms和-Xmx,與機器物理內存對比看是否還可以調大。從代碼上檢查是否存在某些對象生命週期過長,持有狀態時間過長的情況,嘗試減少程序運行期的內存消耗。

二、虛擬機棧溢出

1、設置棧大小

-Xss:設置線程堆棧的內存大小。

2、發生棧溢出

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

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

3、分析溢出情況

棧溢出,可能會導致以下兩種異常情況:

  • 要麼是方法的深度太大了,導致了StackOverflowError異常。
  • 要麼是因爲線程的數目太多了,堆棧的空間又比較大,導致了OutOfMemoryError異常,同時會打印unable to create native thread。由於Java的線程是映射到操作系統的內核線程上的,因此可能會導致操作系統假死。

4、解決棧溢出情況

機器內存 = 堆內存 + 直接內存 + 棧大小*進程數量 + 方法區 + 系統佔用內存

通過以上公式可以得出:如果線程數目比較多,又不方便擴充物理內存的情況下,我麼可以通過減少堆內存,和減小棧的內存佔用大小的辦法來達到目標。

三、方法區和運行時常量池溢出

運行時常量池是方法區的一部分,所以發生異常情況相同。

JDK不同的版本表現不一樣。

1、Jdk1.6及之前的版本

String.intern( ):是一個Native方法,它的作用是,如果字符串常量池中已經包含一個等於此String對象的字符串,則返回代表池中這個字符串的String對象,否則,將此String對象包含的字符串添加到常量池中,並且返回此String對象的引用。

方法區存儲在永久代中,通過**-XX:PermSize-XX:MaxPermSize**來限制方法區大小,從而限制常量池的大小。

報錯OutOfMemoryError異常時,後面還會打印PermGen space信息。

2、Jdk1.7及之後的版本

以下代碼在Jdk1.6和1.7中得到的結果不同:

public static void main(String[] args) {
    String str1 = new StringBuilder("中國").append("釣魚島").toString();
    System.out.println(str1.intern() == str1);

    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == str2);
}

無論jdk的什麼版本,new出來的字符串一定是在堆當中的

在Jdk1.6及之前的版本中,intern( )方法一定是將字符串放到方法區的常量池中的。

Jdk1.7之後,intern( )方法會引用到堆中的第一次出現的實例的地址上,也就是不再會在常量池中複製一份了。

這樣的話,Jdk1.6中,以上代碼,都會返回false,因爲常量池中的地址和堆中的地址肯定不一樣。

在Jdk1.7中,因爲java字符串已經出現過,所以返回false,但是另一個字符串是第一次出現,那麼他的地址也是方法區的常量池中引用的地址,所以返回true。

很多框架,如Spring、Hibernate,在對類進行增強時,都會使用CGLib字節碼技術,增強的類越多,就需要越大的方法區來保證動態生成的Class可以加載入內存。

四、本機直接內存溢出

1、設置直接內存小

-XX:MaxDirectMemorySize:通過此參數來設定。

如果沒有設定的話,則默認與Java堆最大值Xmx一樣。

2、發生內存溢出

以下情況可以考慮檢查一下是不是直接內存溢出:

  • 一個明顯的特徵是在Heap Dump文件中不會看見明顯的異常
  • 如果讀者發現OOM之後Dump文件很小
  • 而程序中又直接或間接使用了NIO。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章