1. 程式人生 > >基於JDK1.8的JVM 記憶體結構【JVM篇三】

基於JDK1.8的JVM 記憶體結構【JVM篇三】

目錄

  • 1、記憶體結構還是執行時資料區?
  • 2、執行時資料區
  • 3、執行緒共享:Java堆、方法區
  • 4、執行緒私有:程式計數器、Java 虛擬機器棧、本地方法棧
  • 5、JVM 記憶體結構總結

在我的上一篇文章別翻了,這篇文章絕對讓你深刻理解java類的載入以及ClassLoader原始碼分析【JVM篇二】中,相信大家已經對java類載入機制有一個比較全面的理解了,那麼類載入之後,位元組碼資料在 Java 虛擬機器記憶體中是如何存放的 ?Java 虛擬機器在為類例項或成員變數分配記憶體是如何分配的 ?是的,這兩個問題就涉及到了JVM 記憶體結構的知識了,那麼這篇文章將進行解答。

@

1、記憶體結構還是執行時資料區?

要解答本篇上面的這些問題,我們首先需要了解一下 Java 虛擬機器的記憶體結構。

從某一角度來說,Java 虛擬機器的記憶體結構 == 執行時資料區,在《Java 虛擬機器規範》中用的是【執行時資料區】術語的,並沒有記憶體結構這麼一說法。記憶體結構只是聽著更加貼切,更加形象,因此知道記憶體結構就是執行時資料區的意思就好了!也沒必要鑽牛角尖糾結這個問題~

2、執行時資料區

JVM被分為三個主要的子系統:類載入器子系統、執行時資料區和執行引擎 。而今天的這篇文章主要講解其中的執行時資料區(Runtime Data Areas)

在 Java 虛擬機器規範中,定義了五種執行時資料區,分別是 Java 堆、方法區、虛擬機器棧、本地方法區、程式計數器 !

順道提一句執行時常量池也會進入方法區,也就是說方法區中就已經包括了常量池。

特別注意其中Java 堆和方法區是 執行緒共享的。其他都是 執行緒私有的。

3、執行緒共享:Java堆、方法區

我們首先來了解了解一下執行緒共享的Java堆和方法區!

3.1、Java堆

Java 堆是所有執行緒共享的,它在虛擬機器啟動時就會被建立

Java 堆是記憶體空間佔據的最大一塊區域了,Java 堆是用來存放物件例項及陣列,也就是說我們程式碼中通過 new 關鍵字 new 出來的物件都存放在這裡。所以這裡也就成為了垃圾回收器的主要活動營地了,於是它就有了一個別名叫做 GC 堆,並且單個 JVM 程序有且僅有一個 Java 堆。根據垃圾回收器的規則,我們可以對 Java 堆進行進一步的劃分,具體 Java 堆記憶體結構如下圖所示:


從上圖可以看出Java 堆並不是單純的一整塊區域,實際上java堆是根據物件存活時間的不同,Java 堆還被分為年輕代、老年代兩個區域,年輕代還被進一步劃分為 Eden 區、From Survivor 0、To Survivor 1 區。並且預設的虛擬機器配置比例是Eden:from :to = 8:1:1 。簡單來說就是:

Java堆 = 老年代 + 新生代

 

新生代 = Eden + S0 + S1

 


預設Eden:from :to = 8:1:1

仔細看過上面的 Java 堆結構圖童鞋可能會發現了-Xms和-Xmn的字樣,是的這個正是控制堆的JVM的引數,實際上我們是可以通過JVM引數動態控制 Java 堆中的各空間大小的,關於JVM的引數是有很多的,但是常用的也就那麼幾個,不多的,用的多了都會很容易記住的,下面我們來講講關於堆的JVM常見的引數:

-Xms: 堆容量初始大小(堆包括新生代和老年代)。 例如:-Xms 20M
-Xmx: 堆總共(最大)大小。 例如:-Xmx 30M
注意:建議將 -Xms 和 -Xmx 設為相同值,避免每次垃圾回收完成後JVM重新分配記憶體!
-Xmn: 新生代容量大小。例如:-Xmn 10M
-XX: SurvivorRatio 設定引數Eden、form和to的比例 【比例引數Eden、form和to預設是8:1:1】例如:-XX: SurvivorRatio=8 代表比例8:1:1

 

雖然沒有直接設定老年代的引數,但是可以設定堆空間大小和新生代空間大小兩個引數來間接控制:
老年代空間大小 = 堆空間大小 - 年輕代大空間大小

當我們的 Java 堆內有足夠的空間去完成例項分配時,並且堆也無法擴充套件,將會丟擲我們常見的OutOfMemoryError 異常,也就是我們常說的OOM 異常

3.2、 JVM 堆記憶體溢位後,其他執行緒是否可繼續工作?

JVM 堆記憶體溢位後也就是OOM 異常,網上有一道非常火的面試題:JVM 堆記憶體溢位後,其他執行緒是否可繼續工作?

實際上這個問題需要具體的場景分析。但是就一般情況下,發生OOM的執行緒都會終結(除非程式碼寫的太爛),該執行緒持有的物件佔用的heap都會被gc了,釋放記憶體。因為發生OOM之前要進行gc,就算其他執行緒能夠正常工作,也會因為頻繁gc產生較大的影響。

也就是說發生OOM的執行緒一般情況下會死亡,也就是會被終結掉,該執行緒持有的物件佔用的heap都會被gc了,釋放記憶體。因為發生OOM之前要進行gc,就算其他執行緒能夠正常工作,也會因為頻繁gc產生較大的影響。

3.3、方法區

拿HotSpot 虛擬機器來說,在 JDK1.7的時候,方法區被稱作為永久代, 從JDK1.8開始,Metaspace (元空間)也就是我們所謂的方法區!

 


也就是說,如果你身邊的小夥伴還在說著永久代,那絕壁是在扯1.8之前的概念了,1.8之後已經廢棄了永久代這個概念!

方法區(Method Area)與上面講的Java堆一樣,都是各個執行緒共享的,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。

Java虛擬機器規範中是這樣定義方法區的:
它儲存了每個類的結構資訊,例如執行時常量池、欄位、方法資料、建構函式和普通方法的位元組碼內容,還包括一些在類、例項、介面初始化時用到的特殊方法。

3.4、JDK1.8 之前的方法區

就以HotSpot 虛擬機器來說,在 JDK1.8 之前,方法區也被稱作為永久代,這個方法區會發生我們常見的 java.lang.OutOfMemoryError: PermGen space 異常,注意是永久代異常資訊,我們也可以通過啟動引數來控制方法區的大小:

-XX:PermSize 設定方法區最小空間
-XX:MaxPermSize 設定方法區最大空間

在JDK7之前的HotSpot虛擬機器中,納入字串常量池的字串被儲存在永久代中,因此導致了一系列的效能問題和記憶體溢位錯誤。特別突出的例子就是String的intern()方法

3.5、JDK1.8 之後的方法區

JDK8之後就沒有永久代這一說法變成叫做元空間(meta space),而且將老年代與元空間剝離。元空間放置於本地的記憶體中,因此元空間的最大空間就是系統的記憶體空間了,從而不會再出現像永久代的記憶體溢位錯誤了,也不會出現洩漏的資料移到交換區這樣的事情。使用者可以為元空間設定一個可用空間最大值,不設定預設根據類的元資料大小動態增加元空間的容量。對於一個 64 位的伺服器端 JVM 來說,其預設的–XX:MetaspaceSize 值為 21MB。也就是說預設的元空間大小是21MB。

==只要類載入器還存活,其載入的類的元資料也是存活的,不會被回收掉!也就是同生共死==

3.6、JDK1.8 之後的方法區為何變化如此之大?

做這個改變呢也許主要是基於以下兩點原因:

1、由於 永久代(PermGen)記憶體經常會溢位,引發惱人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的開發者希望這一塊記憶體可以更靈活地被管理,不要再經常出現這樣的 OOM錯誤。

2、移除 永久代(PermGen)可以促進 HotSpot JVM 與 JRockit VM 的融合,因為 JRockit 沒有永久代。

還有需要注意一點的是永久代的移除並不代表自定義的類載入器洩露問題就解決了。因此,你還必須監控你的記憶體消耗情況,因為一旦發生洩漏,會佔用你的大量本地記憶體,並且還可能導致交換區交換更加糟糕。

4、執行緒私有:程式計數器、Java 虛擬機器棧、本地方法棧

Java 堆以及方法區的資料是共享的,但是有一些部分則是執行緒私有的。執行緒私有部分可以分為:程式計數器、Java 虛擬機器棧、本地方法棧三大部分。

4.1、Java 虛擬機器棧(JVM Stacks)

1、 Java 虛擬機器的每一條執行緒都有自己私有的 Java 虛擬機器棧,這個 Java 虛擬機器棧跟執行緒同時建立,所以它跟執行緒有相同的生命週期。

2、Java 虛擬機器棧描述的是 ==Java 方法==執行的記憶體模型:每一個方法在執行的同時都會建立一個棧幀,用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊,==每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在 Java 虛擬機器棧中的入棧到出棧的過程==。

3、區域性變量表存放了編譯期可知的各種基本資料型別、物件引用和 returnAddress 型別。

1、基本型別:八種基本型別
2、物件引用:reference 型別,它不等同於物件本身,根據不同的虛擬機器實現,它可能是一個指向物件起始地址的引用指標,也可能指向一個代表物件的控制代碼或者其他與此物件相關的位置。
3、 returnAddress 型別:指向了一條位元組碼指令的地址。

其中 64 位長度的 long 和 double 型別的資料會佔用 2 個區域性變數空間(Slot),其餘的資料型別只佔用 1 個。區域性變量表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域性變數空間是完全確定的,在方法執行期間不會改變區域性變量表的大小。

4、Java 虛擬機器棧既允許被實現成固定的大小,也允許根據計算動態來擴充套件和收縮,如果採用固定大小的話,每一個執行緒的 Java 虛擬機器棧容量可以線上程建立的時候獨立選定。在 Java 虛擬機器棧中會發生兩種異常,這個在虛擬機器規範中有指出:

  • 如果執行緒請求分配的棧容量超過 Java 虛擬機器棧允許的最大容量,Java 虛擬機器將會丟擲 StackOverflowError 異常;也就是棧溢位錯誤!方法遞迴呼叫產生StackOverflowError 異常這種結果。
  • 如果 Java 虛擬機器棧可以動態擴充套件,並且在嘗試擴充套件的時候無法申請到足夠的記憶體或者在建立新的執行緒時沒有足夠的記憶體去建立對應的 Java 虛擬機器棧,那麼虛擬機器將會丟擲 OutOfMemoryError 異常。也就是OOM記憶體溢位錯誤!(執行緒啟動過多)

當然,可以通過引數 -Xss 去調整JVM棧的大小!

4.2、本地方法棧(Native Method Stacks)

==和虛擬棧相似,只不過它服務於Native方法==,執行緒私有。當 Java 虛擬機器使用其他語言(例如 C 語言)來實現指令集直譯器時,也會使用到本地方法棧。如果 Java 虛擬機器不支援 natvie 方法,並且自己也不依賴傳統棧的話,可以無需支援本地方法棧。

與 Java 虛擬機器棧一樣,本地方法棧區域也會丟擲 StackOverflowErrorOutOfMemoryError 異常。

HotSpot虛擬機器直接就把本地方法棧和虛擬機器棧合二為一。

4.3、程式計數器

當前執行緒所執行的位元組碼的行號指示器,用於記錄正在執行的虛擬機器位元組指令地址,執行緒私有。

需要特別注意的是,程式計數器是唯一一個在Java虛擬機器規範中沒有規定任何 OutOfMemoryError 情況的區域。

5、JVM 記憶體結構總結


程式計數器:

1、 當前執行緒所執行的位元組碼的行號指示器,用於記錄正在執行的虛擬機器位元組指令地址,執行緒私有。
2、程式計數器是唯一一個在Java虛擬機器規範中沒有規定任何 OutOfMemoryError 情況的區域。

Java虛擬棧:

1、存放基本資料型別、物件的引用、方法出口等,執行緒私有。
2、棧容量超過 Java 虛擬機器棧的最大容量,會丟擲 StackOverflowError 異常;也就是棧溢位錯誤!方法遞迴產生
3、如果 Java 虛擬機器棧可以動態擴充套件,無法申請到足夠的記憶體或者在建立新的執行緒時沒有足夠的記憶體去建立對應的 Java 虛擬機器棧,會丟擲 OutOfMemoryError 異常。也就是OOM記憶體溢位錯誤!(執行緒啟動過多)
4、引數 -Xss 調整JVM棧的大小

Native方法棧:

1、和虛擬棧相似,只不過它服務於Native方法,執行緒私有。
2、HotSpot虛擬機器直接就把本地方法棧和虛擬機器棧合二為一。

Java堆:

java記憶體最大的一塊,所有物件例項、陣列都存放在java堆,GC回收的地方,執行緒共享。

 

Java堆 = 老年代 + 新生代

 

新生代 = Eden + S0 + S1

 


預設Eden:from :to = 8:1:1
方法區:

1、存放已被載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼資料等,回收目標主要是常量池的回收和型別的解除安裝,各執行緒共享
2、方法區在JDK1.7的時候叫做永久代,到JDK1.8之後廢棄了永久代改為元空間(meta space)

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回覆!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔...

相關推薦

基於JDK1.8的JVM 記憶體結構JVM

目錄 1、記憶體結構還是執行時資料區? 2、執行時資料區 3、執行緒共享:Java堆、方法區 4、執行緒私有:程式計數器、Java 虛擬機器棧、本地方法棧 5、JVM 記憶體結構總結

別翻了,這文章絕對讓你深刻理解java類的載入以及ClassLoader原始碼分析JVM

目錄 1、什麼是類的載入(類初始化) 2、類的生命週期 3、介面的載入過程 4、解開開篇的面試題 5、理解首次主動使用 6、類載入器 7、關於名稱空間

3、非線性結構--樹與二叉樹——數據結構基礎

位置 enter 深度 基礎 表達式 左右 -a 基礎篇 先序遍歷 非線性結構--樹與二叉樹 二叉樹的基礎知識:         二叉樹的特點:             1、每個結點的度<=2             2、二叉樹是有序樹         二叉樹的五種不

JVM學習筆記(一)jvm初體驗-記憶體溢位問題分析及解決方案

####1、開始 建立Main類和Demo類,在Main類的main方法中建立List,並向List中無限建立Demo物件,造成記憶體溢位, 並輸出記憶體溢位錯誤檔案在專案目錄下,為了使等待時間減小,設定執行堆記憶體大小。 ####2、建立Demo類 package com.ch

JVM讀書筆記- Java記憶體區域

Java虛擬機器在執行Java程式的過程中會把它所管理的記憶體分為若干個區域。 1. 程式計數器 作用:通過改變這個計數器的值來選取下一條需要執行的指令。 如果正在執行的是Java方法,那麼計數器記錄的是正在執行的虛擬機器位元組碼指令地址;如果正在執

這貨到底還是不是垃圾?垃圾回收GC演算法JVM

目錄 1、判斷物件是否存活的JVM兩種計數演算法 2、垃圾收集演算法 3、垃圾回收演算法小結 垃圾收集 Garbage Collection 通常被稱為“GC”, 在jvm 中,程式計數器、虛擬

JVM命令系列jmap

mark bsp null 參考 nfa location bootstra 內容 遠程debug 命令基本概述 Jmap是一個可以輸出所有內存中對象的工具,甚至可以將VM 中的heap,以二進制輸出成文本。打印出某個java進程(使用pid)內存內的,所有‘對象’的情況(

JVM虛擬機(1)---常用JVM配置參數

str spa void 經歷 color borde free TE per 常用JVM配置參數 常用JVM配置參數主要有:Trace跟蹤參數、堆的分配參數、棧的分配參數。 一、Trace跟蹤參數 跟蹤參數用於跟蹤監控JVM,對於開發人員

Docker之一Docker鏡像及容器

官方 bin 方便 load 停止 pac 例如 exec cpu使用率 一、Docker核心概念 1、鏡像(image) Docker的鏡像是創建容器的基礎,類似虛擬機的快照,可以理解為是一個面向Docker容器引擎的只讀模板,比如一個鏡像可以是一個完整的centos操

DockerMesos+Zookeeper+Marathon+Docker實戰實驗

開發工具 簡化 mark too 資源調度 ext 網絡 上線 root Apache Mesos概述 不同的分布式運算框架(spark,hadoop,ES,MPI,Cassandra,etc.)中的不同任務往往需要的資源(內存,CPU,網絡IO等)不同,它們運行在同一個集

Java03具體類 & 抽象類 & 介面 —— 繼承extends & 實現Implement小結

一、具體類 & 抽象類 $ 介面 從組成上來看: 具體類包含:屬性、構造器、具體方法、初始化塊(靜態語句塊static{ } 、動態語句塊{ })、內部類 ; 抽象類包含:屬性、構造器、具體方法、抽象方法(需要用public abstract修飾); 介面包含:屬性、抽象方法(一

Java04抽象方法中可以有static屬性和方法嗎?小結

抽象方法中可以有Static屬性和方法嗎? 對於這個問題,網上眾說紛紜,其實,抽象類中是可以包含static屬性和static方法的,最典型的例子:Calendar類 下面直接上Calendar類的原始碼: public abstract class Calendar impleme

Web08Web中MVC設計理念 & 經典層架構 & 五大主流框架

一、Web的MVC設計理念和MVC框架: 之前在Web篇04中已經詳述了Servlet、TomCat、JSP和Web.xml之間的聯絡; (1)瀏覽器傳送請求到收到響應,簡要的過程如下: 瀏覽器傳送請求後,由Web.xml中規定的協議,進入TomCat中特定的Servlet,伺服器先

Web07如何理解架構、框架、設計模式

架構、框架和設計模式是Web初學者比較難理解和表述的三個概念,在查閱了大量前人寫的部落格和資料後,將這三個概念和Web的經典三層架構搭配講解,總結如下: 一、架構 FrameWork 架構可以理解成對客戶的需求進行拆分,抽象出不同的元件,不同的抽象元件完成不同的功能,偏於設計一個草圖

Java02二進位制、十進位制、十六進位制之間的相互轉換

1. 二進位制 -> 十六進位制 eg: 二進位制數 1001 1010; 通過1248賦值法,即 1001 1010 8421 8421 --------------- 1+8=9 , 2+8=10; 即二進位制數1001 1010,對應十六進位制的數為 o

Web03Spring框架下,servlet響應的res在jsp頁面中顯示時,防止亂碼的操作

首先,在Spring軟體中,找到Web工程,src下的對應的servlet; 在servlet中獲取請求引數之後,給瀏覽器傳送響應之前; 需要鍵入一段程式碼即可,如下: response.setContentType("text/html;charset=UTF-8"); 括號內的

Web02Get請求和Post請求的區別

Web的面試題中經常會出現這麼一道題,請簡述Get請求和Post請求的區別,標準答案如下: get在瀏覽器回退時是無害的,post會再次提交請求; get產生的url地址可以被bookmark(加入標籤),post不可以; get請求只能進行url編碼,post可以進

Web01域名、url、網站名的區別

很多初學Web的人可能對,域名、url和網站名, 這三個概念比較模糊,下面以我們日常熟悉的一個網址樣式為例來講解:eg: http://mail.163.com/index.html http://,這部分是協議(即HTTP超文字傳輸協議,它是網頁在網上傳輸的協議); ma

Web06JavaScript、JQuery、Ajax的區分

區分這三兄弟之前,我們先來看一下Web前端和Web後端的簡要結構和區別: Web前端的三要素: HTML(內容), CSS(排版), JS(行為,包含JavaScript和JQuery,實現網頁動態效果); Web後端:負責與資料庫互動,實現功能,資料儲存等;一般要求會寫java程式碼,會寫

Web05JSP、JSP表示式、JSP四個域、EL表示式、EL四個域 之間的區別

一、JSP Java ServerPages,即java伺服器頁面,本質是Servelt,存放在伺服器中,用於頁面顯示; 它的主要構成有HTML網頁程式碼、Java程式碼片段、JSP標籤幾部分組成,字尾是.jsp。 (1)JSP指令碼元素:是嵌入到JSP中Java程式碼段,格式以**<