【轉】深入JVM系列(一)之記憶體模型與記憶體配置

來源:互聯網
上載者:User

標籤:

http://lovnet.iteye.com/blog/1825324

 

一、JVM記憶體地區劃分 大多數 JVM 將記憶體地區劃分為  Method Area(Non-Heap), Heap, Program Counter RegisterJava Method Stack,Native Method Stack 和 Direct Memomry(注意 Directory Memory 並不屬於 JVM 管理的記憶體地區)。前三者一般譯為:方法區、堆、程式計數器。但不同的資料和書籍上對於後三者的中文譯名不盡相同,這裡將它們分別譯作: Java 方法棧、原生方法棧和直接記憶體區。對於不同的 JVM,記憶體地區劃分可能會有所差異,比如 Hot Spot 就將 Java 方法棧和原生方法棧合二為一,我們可以統稱為 方法棧(Method Stack)。  首先我們熟悉一下一個一般性的 Java 程式的工作過程。一個 Java 來源程式檔案,會被編譯為位元組碼檔案(以 class 為副檔名),然後告知 JVM 程式的運行入口,再被 JVM 通過位元組碼解譯器載入運行。那麼程式開始運行後,都是如何涉及到各記憶體地區的呢? 概括地說來,JVM 每遇到一個線程,就為其分配一個程式計數器、Java 方法棧和原生方法棧。當線程終止時,兩者所佔用的記憶體空間也會被釋放掉。棧中儲存的是棧幀,可以說每個棧幀對應一個“運行現場”。在每個“運行現場”中,如果出現了一個局部對象,則它的執行個體資料被儲存在堆中,而類資料被儲存在方法區。 二、指令、方法與屬性 在講各部分之前,我們首先要搞清楚的是 什麼是資料以及什麼是指令。然後要搞清楚 對象的方法和對象的屬性分別儲存在哪裡。 1)方法本身是指令的 作業碼部分,儲存在Stack中; 2)方法內部變數作為指令的 運算元部分,跟在指令的作業碼之後,儲存在Stack中(實際上是簡單類型儲存在Stack中,物件類型在Stack中儲存地址,在Heap 中儲存值);上述的指令作業碼和指令運算元構成了完整的Java 指令。 3)對象 執行個體包括其 屬性值作為資料,儲存在資料區Heap 中。 非靜態對象屬性作為對象執行個體的一部分儲存在Heap 中,而對象執行個體必須通過Stack中儲存的地址指標才能訪問到。因此能否訪問到對象執行個體以及它的非靜態屬性值完全取決於能否獲得對象執行個體在Stack中的地址指標。 非靜態方法和靜態方法的區別: 非靜態方法有一個和靜態方法很重大的不同:非靜態方法有一個隱含的傳入參數,該參數是JVM給它的,和我們怎麼寫代碼無關,這個隱含的參數就是 對象執行個體在Stack中的地址指標。因此非靜態方法(在Stack中的指令代碼)總是可以找到自己的專用資料(在Heap 中的對象屬性值)。當然非靜態方法也必須獲得該隱含參數,因此非靜態方法在調用前,必須先 new一個對象執行個體,獲得Stack中的地址指標,否則JVM將無法將隱含參數傳給非靜態方法。 靜態方法無此隱含參數,因此也不需要new對象,只要class檔案被ClassLoader load進入JVM的Stack,該靜態方法即可被調用。當然此時靜態方法是存取不到Heap 中的對象屬性的。  總結一下該過程:當一個class檔案被ClassLoader load進入JVM後,方法指令儲存在Stack中,此時Heap 區沒有資料。然後程式技術器開始執行指令,如果是靜態方法,直接依次執行指令代碼,當然此時指令代碼是不能訪問Heap 資料區的;如果是非靜態方法,由於隱含參數沒有值,會報錯。因此在非靜態方法執行前,要先new對象,在Heap 中分配資料,並把Stack中的地址指標交給非靜態方法,這樣程式技術器依次執行指令,而指令代碼此時能夠訪問到Heap 資料區了。 靜態屬性和動態屬性: 前面提到對象執行個體以及動態屬性都是儲存在Heap 中的,而Heap 必須通過Stack中的地址指標才能夠被指令(類的方法)訪問到。因此可以推斷出:靜態屬性是儲存在Stack中的,而不同於動態屬性儲存在Heap 中。正因為都是在Stack中,而Stack中指令和資料都是定長的,因此很容易算出位移量,也因此不管什麼指令(類的方法),都可以訪問到類的靜態屬性。也正因為靜態屬性被儲存在Stack中,所以具有了全域屬性。 在JVM中,靜態屬性儲存在Stack指令記憶體區,動態屬性儲存在Heap資料記憶體區。 三、Stack 棧 Stack(棧)是JVM的記憶體指令區。Stack管理很簡單,push一定長度位元組的資料或者指令,Stack指標壓棧相應的位元組位移;pop一定位元組長度資料或者指令,Stack指標彈棧。Stack的速度很快,管理很簡單,並且每次操作的資料或者指令位元組長度是已知的。所以Java 基礎資料型別 (Elementary Data Type),Java 指令代碼,常量都儲存在Stack中。 棧也叫棧記憶體,是 Java 程式的運行區,是線上程建立時建立,它的 生命期是跟隨線程的生命 ,線程結束棧記憶體也就釋放,對於棧來說不存在記憶體回收問題,只要線程一結束,該棧就 Over。 那麼棧中存的是那些資料呢?又什麼是格式呢? 棧中的資料都是以 棧幀(Stack Frame)的格式存在,棧幀是一個記憶體區塊,是一個資料集,是一個有關方法(Method)和運行期資料的資料集,當一個方法 A 被調用時就產生了一個棧幀 F1,並被壓入到棧中,A 方法又調用了 B 方法,於是產生棧幀 F2 也被壓入棧,執行完畢後,先彈出 F2棧幀,再彈出 F1 棧幀,遵循“ 先進後出”原則。 那棧幀中到底存在著什麼資料呢?棧幀中主要儲存 3 類資料: 本地變數(Local Variables),包括輸入參數和輸出參數以及方法內的變數; 棧操作(Operand Stack),記錄出棧、入棧的操作; 棧幀資料(Frame Data),包括類檔案、方法等等。光說比較枯燥,我們畫個圖來理解一下 Java棧,如所示:  四、Heap 堆 Heap(堆)是JVM的記憶體資料區。Heap 的管理很複雜,每次分配不定長的記憶體空間,專門用來儲存對象的執行個體。在Heap 中分配一定的記憶體來儲存對象執行個體,實際上也只是儲存對象執行個體的屬性值,屬性的類型和對象本身的類型標記等,並不儲存對象的方法(方法是指令,儲存在Stack中),在Heap 中分配一定的記憶體儲存對象執行個體和對象的序列化比較類似。而對象執行個體在Heap 中分配好以後,需要在Stack中儲存一個4位元組的Heap 記憶體位址,用來定位該對象執行個體在Heap 中的位置,便於找到該對象執行個體。 Java中堆是由 所有的線程共用的一塊記憶體地區。 4.1 Generation JVM堆一般又可以分為以下三部分:◆ Perm Perm代主要儲存class,method,filed對象,這部門的空間一般不會溢出,除非一次性載入了很多的類,不過在涉及到熱部署的應用伺服器的時候,有時候會遇到java.lang.OutOfMemoryError : PermGen space 的錯誤,造成這個錯誤的很大原因就有可能是每次都重新部署,但是重新部署後,類的class沒有被卸載掉,這樣就造成了大量的class對象儲存在了perm中,這種情況下,一般重新啟動應用伺服器可以解決問題。 ◆ Tenured Tenured區主要儲存生命週期長的對象,一般是一些老的對象,當一些對象在Young複製轉移一定的次數以後,對象就會被轉移到Tenured區,一般如果系統中用了application層級的緩衝,緩衝中的對象往往會被轉移到這一區間。 ◆ Young Young區被劃分為三部分,Eden區和兩個大小嚴格相同的Survivor區,其中Survivor區間中,某一時刻只有其中一個是被使用的,另外一個留做垃圾收集時複製對象用,在Young區間變滿的時候,minor GC就會將存活的對象移到閒置Survivor區間中,根據JVM的策略,在經過幾次垃圾收集後,任然存活於Survivor的對象將被移動到Tenured區間。 4.2 Sizing the Generations JVM提供了相應的參數來對記憶體大小進行配置。正如上面描述,JVM中堆被分為了3個大的區間,同時JVM也提供了一些選項對Young,Tenured的大小進行控制。 ◆ Total Heap -Xms :指定了JVM初始啟動以後初始化記憶體 -Xmx:指定JVM堆得最大記憶體,在JVM啟動以後,會分配-Xmx參數指定大小的記憶體給JVM,但是不一定全部使用,JVM會根據-Xms參數來調節真正用於JVM的記憶體 -Xmx -Xms之差就是三個Virtual空間的大小 ◆ Young Generation -XX:NewRatio=8意味著tenured 和 young的比值8:1,這樣eden+2*survivor=1/9 堆記憶體 -XX:SurvivorRatio=32意味著eden和一個survivor的比值是32:1,這樣一個Survivor就占Young區的1/34. -Xmn 參數設定了年輕代的大小 ◆ Perm Generation -XX:PermSize=16M -XX:MaxPermSize=64M Thread Stack -XX:Xss=128K 五、The pc Register程式計數器寄存器 JVM支援多個線程同時運行。每個JVM都有自己的程式計數器。在任何一個點,每個JVM線程執行單個方法的代碼,這個方法是線程的當前方法。如果方法不是native的,程式計數器寄存器包含了當前執行的JVM指令的地址,如果方法是 native的,程式計數器寄存器的值不會被定義。 JVM的程式計數器寄存器的寬度足夠保證可以持有一個返回地址或者native的指標。 六、Method Area 方法區 Object Class Data(類定義資料) 是儲存在方法區的。除此之外,常量、靜態變數、JIT 編譯後的代碼也都在方法區。正因為方法區所儲存的資料與堆有一種類比關係,所以它還被稱為 Non-Heap。方法區也可以是記憶體不連續的地區組成的,並且可設定為固定大小,也可以設定為可擴充的,這點與堆一樣。 方法區內部有一個非常重要的地區,叫做運行時常量池(Runtime Constant Pool,簡稱 RCP)。在位元組碼檔案中有常量池(Constant Pool Table),用於儲存編譯器產生的字面量和符號引用。每個位元組碼檔案中的常量池在類被載入後,都會儲存到方法區中。值得注意的是,運行時產生的新常量也可以被放入常量池中,比如 String 類中的 intern() 方法產生的常量。 6.1 常量池 (constant pool) 常量池指的是在編譯期被確定,並被儲存在已編譯的.class檔案中的一些資料。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,比如: ◆類和介面的全限定名; ◆欄位的名稱和描述符; ◆方法和名稱和描述符。 虛擬機器必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他類型,欄位和方法的符號引用。 對於String常量,它的值是在常量池中的。而JVM中的常量池在記憶體當中是以表的形式存在的, 對於String類型,有一張固定長度的CONSTANT_String_info表用來儲存文字字串值,注意:該表只儲存文字字串值,不儲存符號引 用。說到這裡,對常量池中的字串值的儲存位置應該有一個比較明了的理解了。在程式執行的時候,常量池 會儲存在Method Area,而不是堆中。 七、Java Method StackJava 方法棧與Native Method Stack原生方法棧 第七章內容來源:http://blog.csdn.net/poechant/article/details/7289093 

Java 方法棧也是線程私人的,每個 Java 方法棧都是由一個個棧幀組成的,每個棧幀是一個方法運行期的基礎資料結構,它儲存著局部變數表、運算元棧、動態連結、方法出口等資訊。當線程調用調用了一個 Java 方法時,一個棧幀就被壓入(push)到相應的 Java 方法棧。當線程從一個 Java 方法返回時,相應的 Java 方法棧就彈出(pop)一個棧幀。

 

其中要詳細介紹的是局部變數表,它儲存者各種基礎資料型別 (Elementary Data Type)和對象引用(Object reference)。基礎資料型別 (Elementary Data Type)包括 boolean、byte、char、short、int、long、float、double。對象引用,本質就是一個地址(也可以說是一個“指標”),該地址是堆中的一個地址,通過這個地址可以找到相應的 Object(注意是“找到”,原因會在下面解釋)。而這個地址找到相應 Object 的方式有兩種。一種是該地址儲存著 Pointer to Object Instance Data 和 Pointer to Object Class Data,另一種是該地址儲存著 Object Instance Data,其中又包含有 Pointer to Object Class Data。如下兩圖所示。


 

第一種方式,Java 方法棧中有 Handler Pool 和 Instance Pool。無論哪種方式,Object Class Data 都是儲存在方法區的,Object Instance Data 都是儲存在堆中的。

圖1 控制代碼方式

圖2 直接方式

原生方法棧與 Java 方法棧相類似,這裡不再贅述。

八、JVM運行原理 例子 以上都是純理論,我們舉個例子來說明 JVM 的運行原理,我們來寫一個簡單的類,代碼如下:Java代碼  
  1. public class JVMShowcase {  
  2. //靜態類常量,  
  3. public final static String ClASS_CONST = "I‘m a Const";  
  4. //私人執行個體變數  
  5. private int instanceVar=15;  
  6. public static void main(String[] args) {  
  7. //調用靜態方法  
  8. runStaticMethod();  
  9. //調用非靜態方法  
  10. JVMShowcase showcase=new JVMShowcase();  
  11. showcase.runNonStaticMethod(100);  
  12. }  
  13. //常規靜態方法  
  14. public static String runStaticMethod(){  
  15. return ClASS_CONST;  
  16. }  
  17. //非靜態方法  
  18. public int runNonStaticMethod(int parameter){  
  19. int methodVar=this.instanceVar * parameter;  
  20. return methodVar;  
  21. }  
  22. }  


這個類沒有任何意義,不用猜測這個類是做什麼用,只是寫一個比較典型的類,然後我們來看看 JVM 是如何啟動並執行,也就是輸入 java JVMShow 後,我們來看 JVM 是如何處理的:  向作業系統申請空閑記憶體。JVM 對作業系統說“給我 64M 空閑記憶體”,於是第 1 步,JVM 向作業系統申請空閑記憶體作系統就尋找自己的記憶體配置表,找了段 64M 的記憶體寫上“Java 佔用”標籤,然後把記憶體段的起始地址和終止地址給 JVM,JVM 準備載入類檔案。  分配記憶體記憶體。第 2 步,JVM 分配記憶體。JVM 獲得到 64M 記憶體,就開始得瑟了,首先給 heap 分個記憶體,並且是按照 heap 的三種不同類型分好的,然後給棧記憶體也分配好。  檔案。第 3 步,檢查和分析 class 檔案。若發現有錯誤即返回錯誤。    載入類。第 4 步,載入類。由於沒有指定載入器,JVM 預設使用 bootstrap 載入器,就把 rt.jar 下的所有類都載入到了堆類存的永久儲存區,JVMShow 也被載入到記憶體中。我們來看看棧記憶體,如:Heap 是空,Stack 是空,因為還沒有線程被執行。Class Loader 通知 Execution Enginer 已經載入完畢。    執行引擎執行方法。第 5 步,執行引擎執行 main 方法。執行引擎啟動一個線程,開始執行 main 方法,在 main 執行完畢前,方法區如所示: 在 Method Area 加入了 CLASS_CONST 常量,它是在第一次被訪問時產生的。堆記憶體中有兩個對象 object 和 showcase 對象,如所示:為什麼會有 Object 對象呢?是因為它是 JVMShowcase 的父類,JVM 是先初始化父類,然後再初始化子類,甭管有多少個父類都初始化。在棧記憶體中有三個棧幀,如所示:於此同時,還建立了一個程式計數器指向下一條要執行的語句。  釋放記憶體。運第 6 步,釋放記憶體。運行結束,JVM 向作業系統發送訊息,說“記憶體用完了,我還給你”行結束。

九、JVM 相關問題 問:堆和棧有什麼區別堆和棧有什麼區別有什麼答:堆是存放對象的,但是對象內的臨時變數是存在棧記憶體中,如例子中的 methodVar 是在運行期存放到棧中的。棧是跟隨線程的,有線程就有棧,堆是跟隨 JVM 的,有 JVM 就有堆記憶體。 問:堆記憶體中到底存在著什麼東西?堆記憶體中到底存在著什麼東西?答:對象,包括物件變數以及對象方法。 問:類變數和執行個體變數有什麼區別?類變數和執行個體變數有什麼區別?有什麼區別答:靜態變數是類變數,非靜態變數是執行個體變數,直白的說,有 static 修飾的變數是靜態變數,沒有 static 修飾的變數是執行個體變數。靜態變數存在方法區中,執行個體變數存在堆記憶體中。啟動時就初始化好的,和你這說的不同呀! 問:我聽說類變數是在 JVM 啟動時就初始化好的,和你這說的不同呀!答:那你是道聽途說,信我的,沒錯。的方法(函數)到底是傳值還是傳址值還是傳址? 問:Java 的方法(函數)到底是傳值還是傳址?答:都不是,是以傳值的方式傳遞地址,具體的說原生資料類型傳遞的值,參考型別傳遞的地址。對於未經處理資料類型,JVM 的處理方法是從 Method Area 或 Heap 中拷貝到 Stack,然後運行 frame中的方法,運行完畢後再把變數指拷貝回去。產生? 問:為什麼會產生 OutOfMemory 產生?答:一句話:Heap 記憶體中沒有足夠的可用記憶體了。這句話要好好理解,不是說 Heap 沒有記憶體了,是說新申請記憶體的對象大於 Heap 空閑記憶體,比如現在 Heap 還空閑 1M,但是新申請的記憶體需要 1.1M,於是就會報 OutOfMemory 了,可能以後的對象申請的記憶體都只要 0.9M,於是就只出現一次 OutOfMemory,GC 也正常了,看起來像輕負荷事件,就是這麼回事。 但如果此時 GC 沒有回收就會產生掛起情況,系統不響應了。 問:我產生的對象不多呀,為什麼還會產生 OutOfMemory?我產生的對象不多呀,?答:你繼承層次忒多了,Heap 中 產生的對象是先產生 父類,然後才產生子類,明白不?錯誤分幾種?問:OutOfMemory 錯誤分幾種?答:分兩種,分別是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGenspace”,兩種都是記憶體溢出,heap size 是說申請不到新的記憶體了,這個很常見,檢查應用或調整堆記憶體大小。“PermGen space”是因為永久儲存區滿了,這個也很常見,一般在熱發布的環境中出現,是因為每次發布應用系統都不重啟,久而久之永久儲存區中的死對象太多導致新對象無法申請記憶體,一般重新啟動一下即可。 問:為什麼會產生 StackOverflowError??答:因為一個線程把 Stack 記憶體全部耗盡了,一般是遞迴函式造成的。之間可以互訪嗎? 問:一個機器上可以看多個 JVM 嗎?JVM 之間可以互訪嗎?答:可以多個 JVM,只要機器承受得了。JVM 之間是不可以互訪,你不能在 A-JVM 中訪問B-JVM 的 Heap 記憶體,這是不可能的。在以前老版本的 JVM 中,會出現 A-JVM Crack 後影響到B-JVM,現在版本非常少見。要採用記憶體回收機制,的顯式 問:為什麼 Java 要採用記憶體回收機制,而不採用 C/C++的顯式記憶體管理?的顯 記憶體管理?答:為了簡單,記憶體管理不是每個程式員都能折騰好的。 問:為什麼你沒有詳細介紹記憶體回收機制?為什麼你沒有詳細介紹記憶體回收機制答:記憶體回收機制每個 JVM 都不同,JVM Specification 只是定義了要自動釋放記憶體,也就是說它只定義了記憶體回收的抽象方法,具體怎麼實現各個廠商都不同,演算法各異,這東西實在沒必要深入。中到底哪些地區是共用的?哪些是私人的? 問:JVM 中到底哪些地區是共用的?哪些是私人的?答:Heap 和 Method Area 是共用的,其他都是私人的, 問:什麼是 JIT,你怎麼沒說?,你怎麼沒說?答:JIT 是指 Just In Time,有的文檔把 JIT 作為 JVM 的一個組件來介紹,有的是作為執行引擎的一部分來介紹,這都能理解。Java 剛誕生的時候是一個解釋性語言,別噓,即使編譯成了位元組碼(byte code)也是針對 JVM 的,它需要再次翻譯成原生代碼(native code)才能被機器執行,於是效率的擔憂就提出來了。Sun 為瞭解決該問題提出了一套新的機制,好,你想編譯成原生代碼,沒問題,我在 JVM 上提供一個工具,把位元組碼編譯成原生碼,下次你來訪問的時候直接存取原生碼就成了,於是 JIT 就誕生了,就這麼回事。還有哪些部分是你沒有提到的? 問:JVM 還有哪些部分是你沒有提到的?答:JVM 是一個異常複雜的東西,寫一本磚頭書都不為過,還有幾個要說明的:常量池(constant pool)按照順序存放程式中的常量,:並且進行索引編號的地區。比如 int i =100,這個 100 就放在常量池中。安全管理器(Security Manager):提供 Java 運行期的安全控制,防止惡意攻擊,比如指定讀取檔案,寫入檔案許可權,網路訪問,建立進程等等,Class Loader 在 Security Manager 認證通過後才能載入 class 檔案的。方法索引表(Methods table),記錄的是每個 method 的地址資訊,Stack 和 Heap 中的地址指標其實是指向 Methods table 地址。 問:為什麼不建議在程式中顯式的生命 System.gc()??答:因為顯式聲明是做堆記憶體全掃描,也就是 Full GC,是需要停止所有的活動的(Stop TheWorld Collection),你的應用能承受這個嗎? 問:JVM 有哪些調整參數?答:非常多,自己去找,堆記憶體、棧記憶體的大小都可以定義,甚至是堆記憶體的三個部分、新生代的各個比例都能調整。

 

【轉】深入JVM系列(一)之記憶體模型與記憶體配置

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.