java的運行時數據區域

最近在看《深入理解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來調節,類太多有可能撐爆永久代。靜態變量或常量也有可能撐爆方法區。 

Tags: