JVM內存模型 與 JMM內存模型

JVM內存模型(Java Virtual Machine,JVM)java

java虛擬機JVM = 類加載器(classloader) + 執行引擎(execution engine) + 運行時數據區域(runtime data area)程序員

** 1 、程序計數器(Program Counter Register)**面試

程序計數器是一塊較小的內存空間,它的做用:算法

1.1. 能夠看作是當前線程所執行的字節碼的信號指示器。字節碼解釋器就是經過改變該計數器的值來選取下一條須要執行的字節碼指令, 分支、循環、跳轉、異常處理、線程恢復等基礎功能都需依賴計數器來完成。注:可是,若是當前線程正在執行的是一個本地方法,那麼此時程序計數器爲空。編程

1.2. 在多線程的狀況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候可以知道該線程上次運行到哪兒了。特色:線程私有的, 生命週期隨着線程的建立而建立,隨着線程的結束而死亡。 此內存區域是惟一一個在 Java 虛擬機規範中沒有規定任何 OutOfMemoryError 狀況的區域。數組

** 二、Java 虛擬機棧(Java Virtual Machine Stack)**緩存

Java虛擬機棧與程序計數器同樣,也是線程私有的,其生命週期與線程相同。虛擬機 棧描述的是 Java 方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(StackFrame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型)和 returnAddress 類型(指向了一條字節碼指令的地址)。其中 64 位長度的 long 和 double 會佔用 2個局部變量空間(Slot,一個 32 位),其他數據類型只佔用 1個。局部變量表所需的空間在編譯期間完成分配,當進入一 個方法時,其須要在幀中分配多大的局部變量空間是肯定的,方法運行期間不會改變局部變 量表的大小。局部變量表的建立是在方法被執行的時候,隨着棧幀的建立而建立。並且,局部變量表的大小在編譯時期就肯定下來了,在建立的時候只需分配事先規定好的大小便可。此外,在方法運行的過程當中局部變量表的大小是不會發生改變的。數據結構

Java 虛擬機規範中對該區域規定了兩種異常狀況:多線程

2.1. 如 線 程 請 求 的 深 度 大 於 虛 擬 機 所 允 許 的 深 度 , 棧 溢 出 , 如 遞 歸 時 , 拋 出StackOverflowError 異常。併發

2.2. 虛擬機棧動態擴展沒法申請到足夠的內存時,拋出 OutOfMemoryError 異常。當方法傳遞參數時其實是一個方法將本身棧幀中局部變量表的副本傳遞給另外一個方法棧幀中的局 部變量表(注意是副本,而不是其自己),無論數據類型是什麼(基本類型,引用類型)

三、本地方法棧(Native Method Stack)

Java 虛擬機可能會使用到傳統的棧來支持 native 方法(使用 Java 語言之外的其它語言 編寫的方法)的執行。線程私有的,如 Sun HotSpot 虛擬機直接把本地方法棧和虛擬機棧合 二爲一。

Java 虛擬機規範中對該區域規定了兩種異常狀況:

3.1.如線程請求的深度大於虛擬機所容許的深度,拋出 StackOverflowError 異常。

3.2.虛擬機棧動態擴展沒法申請到足夠的內存時,拋出 OutOfMemoryError 異常。

四、Java 堆(Java Heap)

Java堆是 Java 虛擬機管理內存中最大的一塊,是全部線程共享的內存區域,隨虛擬機的啓動而建立。該區域惟一目的是存放對象實例,幾乎全部對象的實例都在堆裏面分配。Java 虛擬機規範規定,Java堆能夠出於物理上不連續的內存空間中,只要邏輯上連續便可,如同磁盤空間同樣,既能夠實現成固定大小,也能夠是擴展的,當前主流虛擬機都是按照擴展來實現的(經過-Xmx 和-Xms 控制)。Java堆是垃圾收集器管理的主要區域,所以也叫"GC堆",細分一點能夠分爲新生代和老年代;再細緻一點新生代能夠分爲Eden空間、From Survivor空間、ToSurvivor空間。Java 虛擬機規範中對該區域規定了 OutOfMemoryError 異常:若是堆中沒有 內存完成實例分配,而且堆沒法再擴展則拋出 OutOfMemoryError 異常。(當 Ol d 區被放滿的以後,進行 Full GC,Full GC 後,若 Survivor 及 old 區仍然沒法存放 從 Eden 複製過來的部分對象,則出現 OOM 錯誤/或者直接存放大對象、大數組,致使老年代空間不足)

五、方法區(Method Area)

方法區與 Java 堆同樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信 息、常量、靜態變量、即時編譯器編譯後的代碼等數據。在 HotSpot 中用永久代來實現方法區,而其餘虛擬機(如 BEA JRockit、IBM J9 等)是不存在永久代的。Java7 中已經將運行時常量池從永久代移除,在 Java 堆(Heap)中開闢了一塊區域存放運行時常量池。而在 Java8 中,已經完全沒有了永久代,將方法區直接放在一個與堆不相連的本地內存區域,這個區域被叫作元空間。元空間的本質和永久代相似,都是對 JVM 規範中方法區的實現。不過元空間與永久代 之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以,默認狀況下,元 空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小:-XX:MetaspaceSize,初始空間大小。-XX:MaxMetaspaceSize,最大空間,默認是沒有 限制的。Java 虛擬機規範中對方法區規定了 OutOfMemoryError 異常: 若是方法區的內存空間 不能知足內存分配請求,那 Java 虛擬機將拋出一個 OutOfMemoryError 異常。

六、運行時常量池(Runtime Constant Pool)

運行時常量池是方法區的一部分。線程共享。Class 文件中除了有類的版本、字段、方 法、接口等信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面常量和符號引 用,這部份內容在類加載後存放到方法區的常量池中。static 修飾的靜態變量也存放在方法區中,但不是在常量池中(不能修飾局部變量),不 能在一個方法內部定義 static 變量(final 能夠),只能定義爲成員變量。當這個類被Java虛擬機加載後,class文件中的常量就存放在方法區的運行時常量池中。並且在運行期間,能夠向常量池中添加新的常量。如:String類的intern()方法就能在運行期間向常量池中添加字符串常量。當運行時常量池中的某些常量沒有被對象引用,同時也沒有被變量引用,那麼就須要垃圾收集器回收。Java 虛擬機規範中對該區域規定了 OutOfMemoryError 異常: 當常量池沒法申請到內 存時拋出 OutOfMemoryError 異常。

七、直接內存

直接內存是除Java虛擬機以外的內存,但也有可能被Java使用。在NIO中引入了一種基於通道和緩衝的IO方式。它能夠經過調用本地方法直接分配Java虛擬機以外的內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象直接操做該內存,而無需先將外面內存中的數據複製到堆中再操做,從而提高了數據操做的效率。直接內存的大小不受Java虛擬機控制,但既然是內存,當內存不足時就會拋出OOM異常。

八、棧幀

棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區 的虛擬機棧的棧元素。棧幀存儲了方法的局部變量表,操做數棧,動態鏈接和方法返回地址 等信息。第一個方法從調用開始到執行完成,就對應着一個棧幀在虛擬機棧中從入棧到出棧 的過程。在編譯代碼的時候,棧幀中須要多大的局部變量表,多深的操做數棧都已經徹底確 定了,而且寫入到了方法表的 Code 屬性中,所以一個棧幀須要分配多少內存,不會受到程 序運行期變量數據的影響,而僅僅取決於具體虛擬機的實現。一個線程中的方法調用鏈可能會很長,不少方法都同時處理執行狀態。對於執行引擎來 講,活動線程中,只有虛擬機棧頂的棧幀纔是有效的,稱爲當前棧幀(Current Stack Frame),這個棧幀所關聯的方法稱爲當前方法(Current Method)。8.一、局部變量表

Java內存模型(Java Memory Model ,JMM)

就是一種符合內存模型規範的,屏蔽了各類硬件和操做系統的訪問差別的,保證了Java程序在各類平臺下對內存的訪問都能獲得一致效果的機制及規範。目的是解決因爲多線程經過共享內存進行通訊時,存在的原子性、可見性(緩存一致性)以及有序性問題。

基礎概念:

一、原子性

線程是CPU調度的基本單位。CPU有時間片的概念,會根據不一樣的調度算法進行線程調度。因此在多線程場景下,就會發生原子性問題。由於線程在執行一個讀改寫操做時,在執行完讀改以後,時間片耗完,就會被要求放棄CPU,並等待從新調度。這種狀況下,讀改寫就不是一個原子操做。即存在原子性問題。

二、緩存一致性

在多核CPU,多線程的場景中,每一個核都至少有一個L1 緩存。多個線程訪問進程中的某個共享內存,且這多個線程分別在不一樣的核心上執行,則每一個核心都會在各自的caehe中保留一份共享內存的緩衝。因爲多核是能夠並行的,可能會出現多個線程同時寫各自的緩存的狀況,而各自的cache之間的數據就有可能不一樣。 在CPU和主存之間增長緩存,在多線程場景下就可能存在緩存一致性問題,也就是說,在多核CPU中,每一個核的本身的緩存中,關於同一個數據的緩存內容可能不一致。

三、有序性

除了引入了時間片之外,因爲處理器優化和指令重排等,CPU還可能對輸入代碼進行亂序執行,好比load->add->save 有可能被優化成load->save->add 。這就是有序性問題。 多CPU多級緩存致使的一致性問題、CPU時間片機制致使的原子性問題、以及處理器優化和指令重排致使的有序性問題等,都硬件的不斷升級致使的。那麼,有沒有什麼機制能夠很好的解決上面的這些問題呢? 最簡單直接的作法就是廢除處理器和處理器的優化技術、廢除CPU緩存,讓CPU直接和主存交互。可是,這麼作雖然能夠保證多線程下的併發問題。可是,這就有點因噎廢食了。 因此,爲了保證併發編程中能夠知足原子性、可見性及有序性。有一個重要的概念,那就是——內存模型。 爲了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。經過這些規則來規範對內存的讀寫操做,從而保證指令執行的正確性。它與處理器有關、與緩存有關、與併發有關、與編譯器也有關。他解決了CPU多級緩存、處理器優化、指令重排等致使的內存訪問問題,保證了併發場景下的一致性、原子性和有序性。

針對上面的這些問題,不一樣的操做系統都有不一樣的解決方案,而Java語言爲了屏蔽掉底層的差別,定義了一套屬於Java語言的內存模型規範,即Java內存模型。 Java內存模型規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程間變量的傳遞均須要本身的工做內存和主存之間進行數據同步進行。 而JMM就做用於工做內存和主存之間數據同步過程。他規定了如何作數據同步以及何時作數據同步。 

Java內存模型的實現

瞭解Java多線程的朋友都知道,在Java中提供了一系列和併發處理相關的關鍵字,好比volatile、synchronized、final、concurren包等。其實這些就是Java內存模型封裝了底層的實現後提供給程序員使用的一些關鍵字。 在開發多線程的代碼的時候,咱們能夠直接使用synchronized等關鍵字來控制併發,歷來就不須要關心底層的編譯器優化、緩存一致性等問題。因此,Java內存模型,除了定義了一套規範,還提供了一系列原語,封裝了底層實現後,供開發者直接使用。 本文並不許備把全部的關鍵字逐一介紹其用法,由於關於各個關鍵字的用法,網上有不少資料。讀者能夠自行學習。本文還有一個重點要介紹的就是,咱們前面提到,併發編程要解決原子性、有序性和一致性的問題,咱們就再來看下,在Java中,分別使用什麼方式來保證。

原子性: 在Java中,爲了保證原子性,提供了兩個高級的字節碼指令monitorenter和monitorexit。在synchronized的實現原理文章中,介紹過,這兩個字節碼,在Java中對應的關鍵字就是synchronized。 所以,在Java中可使用synchronized來保證方法和代碼塊內的操做是原子性的。

可見性: Java內存模型是經過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值的這種依賴主內存做爲傳遞媒介的方式來實現的。 Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改後能夠當即同步到主內存,被其修飾的變量在每次是用以前都從主內存刷新。所以,可使用volatile來保證多線程操做時變量的可見性。 除了volatile,Java中的synchronized和final兩個關鍵字也能夠實現可見性。只不過實現方式不一樣,這裏再也不展開了。

有序性: 在Java中,可使用synchronized和volatile來保證多線程之間操做的有序性。實現方式有所區別: volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只容許一條線程操做。 好了,這裏簡單的介紹完了Java併發編程中解決原子性、可見性以及有序性可使用的關鍵字。讀者可能發現了,好像synchronized關鍵字是萬能的,他能夠同時知足以上三種特性,這其實也是不少人濫用synchronized的緣由。 可是synchronized是比較影響性能的,雖然編譯器提供了不少鎖優化技術,可是也不建議過分使用。

面試如何回答

當面試官問你:能簡單介紹下你理解的內存模型嗎?

首先,先和麪試官確認一下:您說的內存模型指的是JMM,也就是和併發編程有關的那一個吧? 在獲得確定答覆後,再開始介紹(若是不是,那可能就要回答JVM堆、棧、方法區哪些了....囧...): Java內存模型,實際上是保證了Java程序在各類平臺下對內存的訪問都可以獲得一致效果的機制及規範。目的是解決因爲多線程經過共享內存進行通訊時,存在的原子性、可見性(緩存一致性)以及有序性問題。 除此以外,Java內存模型還提供了一系列原語,封裝了底層實現後,供開發者直接使用。如咱們經常使用的一些關鍵字:synchronized、volatile以及併發包等。 回答到這裏就能夠了,而後面試官可能會繼續追問,而後根據他的追問再繼續往下回答便可。

相關文章
相關標籤/搜索