聊聊JVM內存模型

1、前言

轉眼間也過完了最後一個暑假,最近忙於校招,一直在複習之前學過的一些基礎知識,今天就順便總結一下最近複習的JVM相關的知識。java

2、JVM內存結構

JVM的整體內存結構以下圖所示:算法


大體分爲下面幾個重點的內容,本篇文章咱們主要分析運行時數據區的幾個結構數組

  • 類裝載器(ClassLoader)(用來裝載.class文件)
  • 執行引擎(執行字節碼,或者執行本地方法)
  • 運行時數據區(方法區、Java堆、Java棧、程序計數器、本地方法棧)

2.1 程序計數器

程序計數器是線程私有的區域,每一個線程固然得有個計數器記錄當前執行到哪一個指令。佔用的內存空間小,能夠把它當作是當前線程所執行的字節碼的行號指示器。

若是線程在執行Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令地址;若是執行的是Native方法,這個計數器的值爲空(Undefined)。此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。bash

在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程回覆等基礎功能都須要依賴這個計數器來完成。

每條線程都須要有一個獨立的程序計數器,各個線程之間計數器互不影響,獨立存儲,咱們稱之爲線程私有的區域。jvm

2.2 Java堆

對於大多數應用來講,Java堆是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。該區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。該區域也是垃圾收集器進行垃圾回收的主要區域。

堆的內存空間既能夠固定大小,也能夠在運行時動態地調整,經過以下參數設定初始值和最大值,好比 -Xms256M -Xmx1024M,其中 -X表示它是JVM運行參數,ms是memorystart的簡稱,mx是memory max的簡稱,分別表明最小堆容量和最大堆容量。

能夠經過 -Xms 和 -Xmx 兩個虛擬機參數來指定一個程序的堆內存大小,第一個參數設置初始值,第二個參數設置最大值。

java -Xms1M -Xmx2M HackTheJava複製代碼

它是JVM用來存儲對象實例以及數組值的區域,能夠認爲Java中全部經過new建立的對象的內存都在此分配,堆中的對象的內存須要等待GC進行回收。

Java堆是垃圾收集器管理的主要區域。因爲如今的收集器基本上採用的都是分代收集算法,其主要的思想是針對不一樣類型的對象採起不一樣的垃圾回收算法,能夠將堆分紅兩塊:
  • 新生代(Young Generation)
  • 老年代(Old Generation)
細緻分就是把新生代分爲:Eden空間、From Survivor空間、To Survivor空間。

堆不須要連續內存,而且能夠動態增長其內存,增長失敗會拋出 OutOfMemoryError 異常。

(1) 堆是JVM中全部線程共享的,所以在其上進行對象內存的分配均須要進行加鎖,這也致使了new對象的開銷是比較大的
(2) 全部新建立的Object 都將會存儲在新生代Yong Generation中。若是Young Generation的數據在一次或屢次GC後存活下來,那麼將被轉移到老年代Old Generation。新的Object老是建立在Eden Space。

2.3 Java虛擬機棧

JVM棧是線程私有的,每一個線程建立的同時都會建立JVM棧,生命週期與線程相同。
JVM棧中存放的爲當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean、char、byte、short、int、long、float、double)、部分的返回結果以及棧幀(Stack Frame),非基本類型的對象在JVM棧上僅存放一個指向堆上的地址。


虛擬機棧描述的是Java方法執行的內存模型:每一個Java方法在執行的同時會建立一個棧幀(Stack Frame)用於存儲局部變量表、操縱數棧、常量池引用、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應一個棧幀在虛擬機中入棧到出棧的過程。

能夠經過 -Xss 這個虛擬機參數來指定每一個線程的 Java 虛擬機棧內存大小:

java -Xss512M HackTheJava複製代碼

局部變量表中存放了編譯期可知的各類:
  • 基本數據類型(boolen、byte、char、short、int、 float、 long、double)
  • 對象引用(reference類型,它不等同於對象自己,多是一個指向對象起始地址的指針,也多是指向一個表明對象的句柄或其餘與此對象相關的位置)
  • returnAddress類型(指向了一條字節碼指令的地址)
其中64位長度的long和double類型的數據會佔用2個局部變量空間(Slot),其他數據類型只佔用1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。

2.4 本地方法棧

本地方法棧也是一個線程私有的區域,本地方法棧與Java虛擬機棧的做用類似,二者的區別在於虛擬機棧爲虛擬機執行Java方法服務,而本地方法棧爲虛擬機用到的Native方法服務,此區域用於存儲每一個Native方法調用的狀態。

與虛擬機棧同樣,本地方法棧也會拋出StackOverflowError(棧溢出)和OutOfMemoryError(內存不足)異常。佈局

2.5 方法區

方法區是各個線程共享的內存區域,方法區用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。

和堆同樣不須要連續的內存,而且能夠動態擴展,動態擴展失敗同樣會拋出 OutOfMemoryError 異常。學習

對這塊區域進行垃圾回收的主要目標是對常量池的回收和對類的卸載,可是通常比較難實現。

HotSpot 虛擬機把它當成永久代來進行垃圾回收。可是很難肯定永久代的大小,由於它受到不少因素影響,而且每次 Full GC 以後永久代的大小都會改變,因此常常會拋出 OutOfMemoryError 異常。爲了更容易管理方法區,從 JDK 1.8 開始,移除永久代,並把方法區移至元空間,它位於本地內存中,而不是虛擬機內存中。

(1)在Sun JDK中這塊區域對應的爲PermanetGeneration,又稱爲持久代。
(2)方法區域存放了所加載的類的信息(名稱、修飾符等)、類中的靜態變量、類中定義爲final類型的常量、類中的Field信息、類中的方法信息,當開發人員在程序中經過Class對象中的getName、isInterface等方法來獲取信息時,這些數據都來源於方法區域,同時方法區域也是全局共享的,在必定的條件下它也會被GC,當方法區域須要使用的內存超過其容許的大小時,會拋出OutOfMemory的錯誤信息。

3、經常使用參數

一些jvm中常見的參數配置以下:
  • -Xms64m 最小堆內存 64m.
  • -Xmx128m 最大堆內存 128m.
  • -XX:NewSize=30m 新生代初始化大小爲30m.
  • -XX:MaxNewSize=40m 新生代最大大小爲40m.
  • -Xss=256k 線程棧大小。
  • -XX:+PrintHeapAtGC 當發生 GC 時打印內存佈局。
  • -XX:+HeapDumpOnOutOfMemoryError 發送內存溢出時 dump 內存。
新生代和老年代的默認比例爲 1:2,也就是說新生代佔用 1/3的堆內存,而老年代佔用 2/3 的堆內存。
能夠經過參數 -XX:NewRatio=2 來設置老年代/新生代的比例。

默認的,新生代中各個區域的大小比例爲Edem : from : to = 8 : 1 : 1 (能夠經過參數 –XX:SurvivorRatio 來設定)。spa

4、總結

本篇介紹了JVM虛擬機中運行時數據區的五個內存區域,這些地方也是咱們平時開發中最常接觸到的地方,因此對其有所掌握瞭解仍是頗有必要的,也有助於咱們在服務出現告警時進行相關的問題排查。線程

有任何問題還請各位指正,互相討論學習。指針


部分參考自:《深刻理解Java虛擬機》

相關文章
相關標籤/搜索