首先,JVM除了程序計數器之外,都可能發生內存溢出OutOfMemoryError(OOM)異常。這里主要對可能發生內存溢出的區域,原因進行總結。
1.JAVA虛擬機棧
虛擬機棧是線程私有的,虛擬機棧主要存儲局部變量。Java虛擬機規范中,規定了此區域會拋出兩種異常:
(1)如果請求棧深度大於虛擬機允許的深度,即涉及到方法層級調用太多,超過一定限度,將拋出StackOverflowError異常;這里說的棧的深度主要是java啟動參數中xss參數,虛擬機棧大小配置;
(2)虛擬機棧動態擴展,如果得不到足夠的內存申請空間,就會拋出OOM異常;虛擬機棧動態擴展,即當棧空間不夠的時候,會自動加大棧的空間,避免StackOverflowError,此時申請空間不足,便會OOM,部分虛擬機具備這個功能;
2.本地方法棧
本地方法棧是為Native方法方法服務的,與虛擬機棧一樣,本地方法棧也會拋出OOM以及StackOverflowError異常;
3.JAVA堆
JAVA堆是內存管理最大的一塊,是所有線程共享的一塊區域,在虛擬機啟動的時候創建,主要存儲對象實例。這塊主要通過啟動參數-Xmx進行配置,如果申請的對象實例大小超過該配置的參數,便出現OOM異常。內存泄漏也會導致該區域的OOM,內存泄漏會導致不能回收的對象停留在堆中,隨着時間推移便會消耗完堆空間出發OOM。
4.方法區
方法區和java堆一樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息,常量、靜態變量和即時編譯器編譯后的代碼數據。當方法區無法滿足內存分配,將拋出OOM異常。
當前的一些框架,如Spring、Hibernate等,會使用CGlib技術對類進行增強,相應地會增加類的大小;
還有一些應用,會動態生成JSP文件,JSP文件是需要編譯成Class文件的,大量的文件也有溢出的可能;
或者開發代碼中往常量池添加過多的常量,也有可能造成常量池溢出。
另外一種可能就是我們的應用本身的類就太多,而方法區設置的容量不足,也會容易溢出。
設置方法區的大小,可通過配置-XX:PremSize 設置最小值,-XX:MaxPremSize設置最大值。
5.直接內存
直接內存(Direct Memory)又稱堆外內存,內存對象分配在Java虛擬機的堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機);
(1)減少了垃圾回收
使用堆外內存的話,堆外內存是直接受操作系統管理( 而不是虛擬機 )。這樣做的結果就是能保持一個較小的堆內內存,以減少垃圾收集對應用的影響。
(2)提升復制速度(io效率)
堆內內存由JVM管理,屬於“用戶態”;而堆外內存由OS管理,屬於“內核態”。如果從堆內向磁盤寫數據時,數據會被先復制到堆外內存,即內核緩沖區,然后再由OS寫入磁盤,使用堆外內存避免了這個操作。
如果堆外內存申請超過,物理內存的限制也會出現OOM異常。