JDK(Java Development Kit)是程序開發者用來來編譯、調試java程序用的開發工具包java
JRE(JavaRuntimeEnvironment,Java運行環境),也就是Java平臺。全部的Java 程序都要在JRE下才能運行。普通用戶只須要運行已開發好的java程序,安裝JRE便可面試
JVM(JavaVirtualMachine,Java虛擬機)是JRE的一部分。它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。JVM有本身完善的硬件架構,如處理器、堆棧、寄存器等,還具備相應的指令系統數組
本文的講解都從這個圖一一開始,你腦海裏先試着回憶一下這個幾個區域的概念,是獨享的仍是共享的?每一個區域都存儲了什麼?哪些區域會被垃圾回收?哪些區域會拋出OOM?哪些區域會拋出SOF?如何避免安全
Java虛擬機定義了在程序執行期間使用的各類運行時數據區域。其中一些數據區域是在Java虛擬機啓動時建立的,僅在Java虛擬機退出時才被銷燬。其餘數據區域是每一個線程的。建立線程時建立每一個線程的數據區域,並在線程退出時銷燬每一個數據區域。數據結構
堆內存中存儲的是全部類實例和數組的內存,在虛擬機啓動時建立,虛擬機結束時銷燬,歸還給操做系統,堆內存中對象的銷燬都JVM自行管理(垃圾收集器),當程序建立對象的愈來愈多時而且這些對象都沒法被回收時,這個區域會拋出OOM異常,而且堆內存是全部線程共享的,因此當多個線程操做堆內存的數據時會有併發問題,要加鎖。多線程
棧分爲虛擬機棧和本地方法棧,首先棧是線程安全的,棧內存隨線程建立而建立,隨線程銷燬而銷燬,棧內存是不須要垃圾回收器進行回收的。線程棧的大小能夠是在虛擬機啓動時指定固定大小,也能夠是自行計算動態擴容的。當指定大小時,線程棧的內存隨着使用而不足時JVM拋出StackOverFlowError,當不指定大小時,線程棧動態擴容時若是沒有足夠的內存不足,JVM將會拋出OOM錯誤。架構
虛擬機棧描述的是Java方法執行的內存模型,每一個方法在執行時會建立一個棧幀用於存儲放法局部變量表,操做數棧,動態連接,出口信息,以下圖,整個棧幀是先入後出。
併發
局部變量表存放了編譯器可知的各類基本數據類型,對象引用(不包含成員變量)每一個局部變量表佔用32位(4個字節),因此long和double會佔用兩個局部變量表,其它類型佔用一個,哪怕byte雖然只有8位,也佔用一個局部變量表,局部變量表所需的內存在編譯期就已經肯定了也就是進入這個方法時就已經肯定了,運行期間不會更改.jvm
操做數棧則存儲方法內一些進行了運算操做後的結果.ide
動態連接,在方法內調用接口,經過字面量連接到具體的實現類,實現Java的動態特性.
出口地址(返回地址),return或者發生Exception等。
本地方法棧虛擬機棧類似,都是線程私有的,安全的,區別就是虛擬機爲虛擬機棧執行Java服務(字節碼服務),而本地方法棧爲虛擬機使用到的Native方法服務,本地方法棧中使用的語言,使用方式,數據結構沒有強制要求。
Java程序是多線程執行的,當一個線程執行字節碼時,忽然CPU切換到另外一個線程,那麼上一個線程執行的上下文信息怎麼保存呢?等到下次再切換到這個線程,從哪裏開始執行呢?這些信息都須要在線程切換時記錄,這就是程序計數器的職責,是每一個線程私有的,線程安全的,因線程建立而建立,因線程銷燬而銷燬,程序計數器其實就是一小塊內存。
程序計數器指向當前線程所執行的字節碼所在的行號,記錄着當前程序運行到哪了字節碼解釋器的工做就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令。分支,循環,跳轉,異常處理,線程回覆等都須要依賴這個計數器來完成
若是一個線程執行一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是一個本地方法,這個計數器的值則爲undefine,此內存區域是惟一一個在Java的虛擬機規範中沒有規定任何OutOfMemoryError異常狀況的區域
默認狀況下,類元數據只受可用的本地內存限制。新參數(MaxMetaspaceSize)用於限制本地內存分配給類元數據的大小。若是沒有指定這個參數,元空間會在運行時根據須要動態調整,
這個區域也是會發生GC的,垃圾回收將在元數據使用達到「MaxMetaspaceSize」參數的設定值時進行,適時地監控和調整元空間對於減少垃圾回收頻率和減小延時是頗有必要的。若是的元空間持續的發生GC說明可能存在類、類加載器致使的內存泄漏或是大小設置不合適,若是這個空間使用達到了MaxMetaspaceSize,但GC沒法回收(全部的類信息都是有用的,因此沒法回收),也會發生OOM錯誤。
String常量池已經從方法區(jdk8之前的叫法)中的運行時常量池分離到堆中了,不在元數據中。
Metaspace由兩部分組成:Klass MetaSpace 和 NoKlass MetaSpace,Klass表明的是
class文件在jvm中運行時的數據結構,NoKlass專門用來存儲Klass相關的其它數據,好比Method和ConstantPool。
new Thread(new Runnable() { @Override public void run() { test(); } public void test(){ Object obj = new Object(); } }).start();
上面這段代碼很簡單,啓動了一個線程,線程的run方法中調用了test方法,test方法中建立一個Objet對象,一塊兒來看一下這段代碼涉及的JVM內存哪些區域,分別存儲了什麼。
首先建立了一個線程,那麼這個線程對應的私有的虛擬機棧內存確定被分配,這個線程的代碼執行中對應的程序計數器內存確定被分配,由於沒有涉及到本地方法,全部本地棧內存不會分配,並且虛擬機棧內存是在編譯器就肯定的。
Test方法執行時,建立一個Object對象,咱們知道obj是一個引用(reference)類型,因此obj保存在Java棧的本地變量表中,而在Java堆中會保存該引用的實例化對象,Java堆中還必須包含能查找到此對象類型數據的地址信息(如對象類型、父類、實現的接口,方法等)這些類型數據則保存在元數據區域中。通常對象引用到對象實例和對象類型指向有兩種方法,一種是句柄池方式,一種是直接指針方式。這兩種對象的訪問方式各有優點,使用句柄訪問方式的最大好處就是reference中存放的是穩定的句柄地址,在對象的移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要修改。使用直接指針訪問方式的最大好處是速度快,它節省了一次指針定位的時間開銷。目前Java默認使用的HotSpot虛擬機採用的即是是第二種方式進行對象訪問的,下面用兩張圖來表述一下這兩種方式。
這張圖是句柄池方式
這張圖是直接指針方式
基本數據類型包括 int short long bolean等,引用類型就是咱們常見的對象,那麼這兩種數據類型內存中是怎麼分配的呢?這個得區別看待,咱們根據下面代碼來分析
class Dog { private int age; } class Test{ public void test(){ Dog dog = new Dog(); dog.age = 2; int age = 1; Integer age = new Integer(3); } }
在Test類中的test方法中,咱們建立了一個Dog對象,這個對象實例是分配在堆上的,dog這個引用是在棧上的,dog中的age在哪裏呢?由於Dog對象實例是在堆上的,全部他的成員變量也是在堆上的。 int age這個變量是棧上的,由於它是局部變量,而且是基本數據類型,Integer age實例是在堆上的,引用是在棧上的,根據這個例子,能夠總結下面兩條基本黃金法則
本文詳細介紹了JVM內存區域的各個狀況,也就是JVM內存模型,也解答了一些常見的面試題和內存分配相關的一些問題,但願可以幫助到讀者更好的瞭解到JVM,可能會有人有些疑問,爲何不說堆內存的分代(年輕代,年老代)問題呢?我認爲這個屬於JVM垃圾回收的方位,分代思想只是解決垃圾收回問題的一種方法,同理,Java8中G1的region也是同樣,都是爲了解決垃圾回收效率和性能問題,會放在JVM垃圾回收一文來講。