以前咱們探討過一個.class文件是如何被加載到jvm中的。可是jvm內又是如何劃份內存的呢?這個內被加載到了那一塊內存中?jvm內存劃分也是面試當中必被問到的一個面試題。java
其實這個問題很是簡單,JVM在運行咱們寫好的代碼時,他是必須使用多塊內存空間的,不一樣的內存空間用來放不一樣的數據,而後配合咱們寫的代碼流程,才能讓咱們的系統運行起來。面試
舉個最簡單的例子,好比我們如今知道了JVM會加載類到內存裏來供後續運行,那麼我問問你們,這些類加載到內存之後,放到哪兒去了呢?想過這個問題嗎?安全
因此JVM裏就必須有一塊內存區域,用來存放咱們寫的那些類。數據結構
包括咱們定義的成員變量,類變量,方法,局部變量等等,都在jvm內存中對應着一塊內存來記錄存儲。併發
在JDK1.8以前的版本里,表明JVM的一塊區域。在1.8版本之後,這塊區域的名字改了,叫作「Matespace」,能夠認爲是「元數據空間」這樣的意思,固然這裏主要存放的仍是咱們本身寫的各類類的相關信息。jvm
舉個栗子。有以下兩個類,People類沒有成員變量,而Student類有一個name的類變量。spa
public class Student{ private static String name = "lisi"; } public class People{ public static void main(){ Student student = new Student(); } }
這兩個類被加載到JVM,就會存放在這個方法區裏面(注意:若是讀過我以前的章節,就會明白這裏的加載表明的是:加載->驗證->準備->解析->初始化,類的全部類變量都會被賦值)。以下圖操作系統
咱們知道,被加載到jvm的類對象是咱們寫的.java文件被編譯以後的.class文件。線程
在編譯事後會將咱們的代碼編譯成計算機能讀懂的字節碼。而這個.calss文件就是,就是咱們代碼編譯好的字節碼了。code
加載到內存之後,字節碼執行引擎就開始工做了。去執行咱們編譯出來的代碼指令,以下圖
此時問題來了,咱們是否是須要一塊內存空間來記錄咱們字節碼執行引擎目前執行到了哪行代碼?這一塊特殊的內存區域就是「程序計數器」
這個程序計數器就是用來記錄當前執行的字節碼指令的位置。
以下圖:
到這裏我相信會有人產生疑惑,就按照當前的代碼順序執行就好了,爲何要記錄執行到哪裏了?
由於咱們寫好的代碼可能會開啓多個線程併發的執行不一樣的代碼。可能當前線程這段代碼尚未執行完畢,就上下文切換到另外一段代碼中。
當線程再次上下文切換到以前的代碼時,就須要一個專門記錄當前線程執行到了哪一條字節碼。因此,每個線程都有這本身的程序計數器。
以下圖:
java代碼在執行的時候,必定是某個線程來執行某個方法中的代碼。
當線程執行到某個方法的時候,若是這個方法有局部變量,那麼就須要一塊區域來存放局部變量的數據信息。這個區域就叫作java虛擬機棧。
每個線程都有一個本身的java虛擬機棧,好比說當執行main方法的時候就會有一個main線程,用來存放main方法中定義的局部變量
public static void main(){ People people = new People(); int i = 9; }
好比上面的main()方法中,其實就有一個"people"的局部變量,他是引用一個People的實例對象的,這個對象咱們先無論他。而後有一個"i"的局部變量。
以下圖:
我想你們應該都知道棧的數據結構,後進先出。當方法執行完畢之後,這個棧楨就會出棧,裏面的局部變量信息就會從內存刪除。因此局部變量是線程安全的。由於只有當前線程能獲取到這個值。
爲何要用後進先出的數據結構?
假設a方法當中同步調用b方法,此時a方法的棧楨先入棧,而後再是b方法的棧楨入棧。b方法執行完畢後,b方法的棧楨出棧,繼續執行a方法。因此使用一個後進先出的棧結構是很是完美的。
此時jvm的內存模型圖以下:
這一塊內存是很是很是重要的。
咱們實例化的全部對象都是存放在這個內存中。這個實例化的對象裏面會包含一些數據,咱們用上面的代碼來作栗子。
public class Student{ private String name = "lisi"; public String getNmae(){ return name; } } public class People{ public static void main(){ Student student = new Student(); student.getName(); } }
仍是這個代碼,當main線程執行main()方法的時候,首先在堆內存中實例化Student對象,而後在局部變量中建立student,student存的是實例化Student對象的內存地址。而後執行Student對象的getName()方法。
以下圖:
由上圖能夠看出來,棧空間是封閉的,是線程安全的,而堆內存中是咱們主要發生線程不安全的地方,由於堆內存的空間全部的線程其實都是能共享的。
此時jvm的內存劃分的最終模型爲:
不少java程序猿對這一塊區域的接觸是很是少的。
其實在JDK的不少底層代碼API中,好比NIO。
若是你去看源碼會發現不少地方的代碼不是java寫的,而是走的native方法去調用本地操做系統裏面的一些方法,可能調用的都是c語言寫的方法。
好比說:public native int hashCode();
在調用這種native方法的時候,就會有線程對應的本地方法棧,這個其實相似於java虛擬機棧。也是存放各類native方法的局部變量表之類的信息。
還有一塊區域,是否是jvm的,經過NIO中的allocateDirect這種API,能夠在jva堆外分配內存空間,而後經過java虛擬機棧裏的DirectByteBuffer來引用和操做堆外內存空間。
基本上jvm的核心內存區域的功能都解釋清楚了,面試能回答到這一個地步應該也能順利經過了。
咱們須要重點關注的是方法區,程序計數器,java虛擬機棧和java堆內存這些內存區域的做用。