最近在看《深入理解Java虛擬機器》,書中給了幾個例子,比較好的說明了幾種OOM(OutOfMemory)產生的過程,大部分的程式設計師在寫程式時不會太關注Java執行時資料區域的結構:
1.程式計數器:執行緒隔離的資料區域,當前執行緒所執行的位元組碼的行號指示器.
PC暫存器( PC register ):每個執行緒啟動的時候,都會建立一個PC(Program Counter,程式計數器)暫存器。PC暫存器裡儲存有當前正在執行的JVM指令的地址。 每一個執行緒都有它自己的PC暫存器,也是該執行緒啟動時建立的。儲存下一條將要執行的指令地址的暫存器是 :PC暫存器。PC暫存器的內容總是指向下一條將被執行指令的地址,這裡的地址可以是一個本地指標,也可以是在方法區中相對應於該方法起始指令的偏移量。
2.java虛擬機器棧:執行緒私有的:java方法執行的執行緒記憶體模型,每個方法被執行的時候,java虛擬機器都同步建立一個棧幀,(Stack Frame)用於儲存區域性變數表,運算元棧,動態諒解,方法出口等資訊.
區域性變數表:存放byte ,short, int ,long ,float, double , boolean,char 以及 物件引用 和 returnAddress型別.
區域性變數槽(Slot):long 和 double佔兩個 其餘只佔一個槽位
3.本地方法棧:虛擬機器使用到的本地方法服務
Java棧的區域很小,只有1M,特點是存取速度很快,所以在stack中存放的都是快速執行的任務,基本資料型別的資料,和物件的引用(reference)。
駐留於常規RAM(隨機訪問儲存器)區域。但可通過它的“棧指標”獲取處理的直接支援。棧指標若向下移,會建立新的記憶體;若向上移,則會釋放那些記憶體。這是一種特別快、特別有效的資料儲存方式,僅次於暫存器。建立程式時,Java編譯器必須準確地知道堆疊內儲存的所有資料的“長度”以及“存在時間”。這是由於它必須生成相應的程式碼,以便向上和向下移動指標。這一限制無疑影響了程式的靈活性,所以儘管有些Java資料要儲存在棧裡——特別是物件控制程式碼,但Java物件並不放到其中。
JVM只會直接對JavaStack(Java棧)執行兩種操作:①以幀為單位的壓棧或出棧;②通過-Xss來設定, 若不夠會丟擲StackOverflowError異常。
1.每個執行緒包含一個棧區,棧中只儲存基本資料型別的資料和自定義物件的引用(不是物件),物件都存放在堆區中
2.每個棧中的資料(原始型別和物件引用)都是私有的,其他棧不能訪問。
3.棧分為3個部分:基本資料型別的變數區、執行環境上下文、操作指令區(存放操作指令)。
棧是存放執行緒呼叫方法時儲存區域性變數表,操作,方法出口等與方法執行相關的資訊,Java棧所佔記憶體的大小由Xss來調節,方法呼叫層次太多會撐爆這個區域。
4.java堆:java堆是垃圾收集器管理的記憶體區域,所有執行緒共享的java堆中可以劃分除多個執行緒私有的分配緩衝區,以提升物件分配是的效率,細分的目的只為了更好地回收記憶體,以及更快的分配記憶體.
類的物件放在heap(堆)中,所有的類物件都是通過new方法建立,建立後,在stack(棧)會建立類物件的引用(記憶體地址)。
一種常規用途的記憶體池(也在RAM(隨機存取儲存器 )區域),其中儲存了Java物件。和棧不同:“記憶體堆”或“堆”最吸引人的地方在於編譯器不必知道要從堆裡分配多少儲存空間,也不必知道儲存的資料要在堆裡停留多長的時間。因此,用堆儲存資料時會得到更大的靈活性。要求建立一個物件時,只需用new命令編輯相應的程式碼即可。執行這些程式碼時,會在堆裡自動進行資料的儲存。當然,為達到這種靈活性,必然會付出一定的代價:在堆裡分配儲存空間時會花掉更長的時間。
JVM將所有物件的例項(即用new建立的物件)(對應於物件的引用(引用就是記憶體地址))的記憶體都分配在堆上,堆所佔記憶體的大小由-Xmx指令和-Xms指令來調節,sample如下所示:
public class HeapOOM {
static class OOMObject{
}
/**
* @param args
*/
public static void main(String[] args) {
List list = new ArrayList();// List類和ArrayList類都是集合類,
// 但是ArrayList可以理解為順序表,
// 屬於線性表。
while (true) {
list.add(new OOMObject());
}
}
}
加上JVM引數 -verbose:gc -Xms10M -Xmx10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError,
就能很快報出OOM異常(記憶體溢位異常):
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
並且能自動生成Dump。
5.執行時常量池:編譯期間生成的各種字面量與符號的引用,
這兒的“靜態”是指“位於固定位置”。程式執行期間,靜態儲存的資料將隨時等候呼叫。可用static關鍵字指出一個物件的特定元素是靜態的。但Java物件本身永遠都不會置入靜態儲存空間。
這個區域屬於方法區。該區域存放類和介面的常量,除此之外,它還存放成員變數和成員方法的所有引用。當一個成員變數或者成員方法被引用的時候,JVM就通過執行常量池中的這些引用來查詢成員變數和成員方法在記憶體中的的實際地址。
6.方法區:
method(方法區)又叫靜態區,存放所有的①類(class),②靜態變數(static變數),③靜態方法,④常量和⑤成員方法。
1.又叫靜態區,跟堆一樣,被所有的執行緒共享。
2.方法區中存放的都是在整個程式中永遠唯一的元素。這也是方法區被所有的執行緒共享的原因。
(順便展開靜態變數和常量的區別: 靜態變數本質是變數,是整個類所有物件共享的一個變數,其值一旦改變對這個類的所有物件都有影響;常量一旦賦值後不能修改其引用,其中基本資料型別的常量不能修改其值。)
Java裡面是沒有靜態變數這個概念的,不信你自己在某個成員方法裡面定義一個static int i = 0;Java裡只有靜態成員變數。它屬於類的屬性。至於他放哪裡?樓上說的是靜態區。我不知道到底有沒有這個翻譯。但是深入JVM裡是翻譯為方法區的。虛擬機器的體系結構:①Java棧,② 堆,③PC暫存器,④方法區,⑤本地方法棧,⑥執行常量池。而方法區儲存的就是一個類的模板,堆是放類的例項(即物件)的。棧是一般來用來函式計算的。隨便找本計算機底層的書都知道了。棧裡的資料,函式執行完就不會儲存了。這就是為什麼區域性變數每一次都是一樣的。就算給他加一後,下次執行函式的時候還是原來的樣子。
方法區的大小由-XX:PermSize和-XX:MaxPermSize來調節,類太多有可能撐爆永久代。靜態變數或常量也有可能撐爆方法區。