章節(jié)內(nèi)容概要
本章主要講解 “Java內(nèi)存區(qū)域與內(nèi)存溢出” 的一些常見原因及分析方法;以應(yīng)對線上環(huán)境服務(wù)器出現(xiàn)內(nèi)存溢出時該知道如何著手去排查。
先了解虛擬機運行時數(shù)據(jù)區(qū)域

程序計數(shù)器:
程序計數(shù)器是一塊較小的內(nèi)存空間,可以把它看做是程序當前執(zhí)行線程字節(jié)碼的行號指示器;因為單核CPU同一時刻只能執(zhí)行一個線程中的指令,為了保證線程切換后可以恢復(fù)到當前線程 “上一次執(zhí)行到的字節(jié)碼位置” 繼而繼續(xù)執(zhí)行。每個線程都擁有一個獨立的程序計數(shù)器,各線程間互不影響。
虛擬機棧:
Java方法執(zhí)行時的內(nèi)存模型,每個方法在被調(diào)用執(zhí)行時都會創(chuàng)建一個叫 “棧幀” 的內(nèi)存塊,用于存儲 局部變量,操作數(shù)棧,動態(tài)鏈接,方法出口等信息。每個方法從調(diào)用至執(zhí)行完成的過程就對于這一個棧幀在虛擬機中入棧到處棧的過程。它與線程的生命周期相同。
本地方法棧:
與“虛擬機?!毕嗨?,不同的是本地方法棧只為虛擬機使用到的 Native 方法服務(wù)。Native 方法可以理解為java調(diào)用的非本平臺的接口,如:調(diào)用C的xxx方法。
堆:
虛擬機使用最多也是分配內(nèi)存最大的一塊,主要用于存放程序中創(chuàng)建的對象實例。
Java虛擬機規(guī)范描述:所有的對象實例以及數(shù)組都要在堆上進行分配。
方法區(qū):
用于存儲已被虛擬機加載的 類信息,常量,靜態(tài)變量,即時編譯器編譯后的代碼等數(shù)據(jù)。
運行時常量池:屬于方法區(qū)的一部分,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容在類加載后進入方法區(qū)的運行時常量池中。
補充部分:
直接內(nèi)存:不屬于虛擬機運行時數(shù)據(jù)區(qū)的一部分,Java NIO包中涉及到可以使用Native函數(shù)庫分配虛擬機之外內(nèi)存的方式。
虛擬機可能出現(xiàn)的內(nèi)存溢出及異常
OutOfMemoryError
-
堆溢出
由于堆中創(chuàng)建的對象數(shù)量達到設(shè)置的最大堆容量限制后出現(xiàn)。表象:java.lang.OutOfMemoryError: Java heap space ..
public class HeapOOM {
/**
* VM Args: -Xms20m -Xmx20m -XX:HeapDumpOnOutOfMemoryError
*/
static class OOMObject {}
public static void main(String[] args) {
List list =new ArrayList();
while(true) {
list.add(new OOMObject());
}
}
}
-
方法區(qū)和運行時常量池溢出
常量池中過多的添加常量或使用了類似CGLib的字節(jié)碼技術(shù),動態(tài)加載過多的Class到方法區(qū),都可能導(dǎo)致如下異常。
表象:java.lang.OutOfMemoryError: PermGen space ..
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstanctPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
StackOverflowError
-
虛擬機棧或本地方法棧溢出
單線程情況,棧中使用內(nèi)存超過設(shè)置內(nèi)存,可能原因: 棧幀太大 或 虛擬機棧容量設(shè)置太小而導(dǎo)致的內(nèi)存無法分配。
表象:java.lang.StackOverflowError
優(yōu)化思路:
配置增加棧內(nèi)存分配大小 (-Xss);
優(yōu)化代碼,減少方法中的(局部變量,操作數(shù)棧..),也就是減小棧幀大小;
多線程情況,創(chuàng)建線程導(dǎo)致的內(nèi)存溢出;
表象:OutOfMemoryError: unable to create new native thread
優(yōu)化思路:
在不能減少線程數(shù)的情況下,只能通過減少最大堆內(nèi)存和減少每個棧的容量來換取更多的線程數(shù);
直接內(nèi)存溢出
此原因?qū)е碌膬?nèi)存溢出一般因使用NIO導(dǎo)致,上文中也提到NIO可直接使用機器內(nèi)存(DirectByteBuffer 類);
如何分析由直接內(nèi)存導(dǎo)致的內(nèi)存溢出,明顯特征是在 Heap Dump 文件中不會看見明顯的異常,如果出現(xiàn) OOM 后 Dump 文件又很小,程序中直接或間接使用了NIO,則可以排查下這方面的原因。
Socket異常
IOException: Too many open files
原因分析: 每個Socket連接都擁有 Receive 和 Send 兩個緩存區(qū),占用大小各種占用一定大?。?如:37KB,25KB),當創(chuàng)建的連接過多時,內(nèi)存已無法進行再分配,就會拋出此異常。SocketException:Connection reset
原因分析: 調(diào)用服務(wù)響應(yīng)時間延遲,導(dǎo)致等待的線程和Socket連接大量創(chuàng)建及等待,超過虛擬機可承受的量時,虛擬機進程崩潰。
優(yōu)化思路:
優(yōu)化服務(wù)提供方響應(yīng)時間;
優(yōu)化系統(tǒng)交互方式,減少系統(tǒng)實時連接,調(diào)整為消息隊列實現(xiàn);
參考資料
書籍:深入理解Java虛擬機,操作系統(tǒng)概念
虛擬機講解連載文章
待更新..