JVM 內存結構介紹整理

Jvm內存模型

  • JVM內存共分爲虛擬機方法區程序計數器本地方法棧五個部分。
    JVM內存模型

虛擬機棧

  • 每個線程都有一個私有的棧,隨着線程創建而創建。每個棧空間都存放着棧幀,每個方法都會創建一個棧幀,棧幀主要存放了局部變量列表(局部變量表主要存放了編譯器可知的各種數據類型[boolean、byte、char、short、int、float、long、double]、對象引用[reference類型,它不同於對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置])、操作數棧、方法出口等信息.

  • 一個線程中方法的調用鏈可能會很長,很多方法都同時處於執行狀態。對於JVM執行引擎來說,在在活動線程中,只有位於JVM虛擬機棧棧頂的元素纔是有效的,即稱爲當前棧幀,與這個棧幀相關連的方法稱爲當前方法,定義這個方法的類叫做當前類。

  • 棧中存放對象的引用
    棧中存放對象的引用

  • 棧幀模型
    棧幀模型

  • 執行引擎運行的所有字節碼指令都只針對當前棧幀進行操作。如果當前方法調用了其他方法,或者當前方法執行結束,那這個方法的棧幀就不再是當前棧幀了。

  • 調用新的方法時,新的棧幀也會隨之創建。並且隨着程序控制權轉移到新方法,新的棧幀成爲了當前棧幀。方法返回之際,原棧幀會返回方法的執行結果給之前的棧幀(返回給方法調用者),隨後虛擬機將會丟棄此棧幀。

  • 當一個方法開始執行的時候,這個方法的操作數棧是空的,在方法的執行過程中,會有各種字節碼指令往操作數棧中寫入內容和讀取數據,即入棧和出棧操作。

  • 棧幀是線程本地的私有數據,不可能在一個棧幀中引用另外一個線程的棧幀。

  • 棧幀數過多及棧深度過大時超過虛擬機定義的所允許深度,就會拋出StackOverflowError異常(無限制申請線程),當虛擬機棧無法申請到足夠的內存,就會拋出OutOfMemoryError異常

本地方法棧

  • 本地方法棧與虛擬機棧的使用原理基本一致,只不過虛擬機棧爲虛擬機執行Java方法(字節碼)服務,本地方法棧爲虛擬機執行Native方法服務

Java堆

  • Java堆是被所有線程 共享的一片區域,該內存區域的唯一目的就是存放對象的實例
  • Java堆是垃圾收集器管理的主要區域,主要分爲年輕代和年老代 == GC算法==
  • Java實例對象在創建時無法再分配堆空間時,就會拋出OutOfMemoryError異常
  • Java堆的大小分配是可以擴展的,啓動參數-Xmx/-Xms可以控制大小
  • 線程共享的堆內存可能會 分出來多個線程私有的分配緩衝區==(TLAB,這是爲了併發分配內存時的髒分配問題,需要使用相關參數來開啓。虛擬機默認使用CAS加上失敗重試機制解決髒分配問題)==

Java堆內存和棧內存 區別

  1. 當創建任何對象時,在堆中創建,堆內存是所有線程共享的,棧內存只是對當前被執行的線程可見
  2. 因爲創建的對象是存儲在堆中,棧空間存儲着對他的引用,還有就是該線程執行方法中的局部變量,局部變量隨着棧的銷燬而銷燬
  3. 兩者的內存管理機制不同,棧內存遵循棧入和棧出原則,堆內存分年輕代和年老代管理,棧內存存儲時間短暫,棧出及被銷燬,對內存可能會常駐,依賴垃圾回收器分析處理
  4. 使用-Xms和-Xmx JVM選項來定義堆內存的啓動大小和最大大小,使用-Xss來定義棧內存大小。
  5. 當棧內存已滿時,運行時拋出,java.lang.StackOverFlowError,如果堆內存已滿,則拋出java.lang.OutOfMemoryError:Java Heap Space錯誤。

方法區

  • 方法區與堆一樣,是線程共享的,用於存儲被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據
  • 方法區的內存很少觸發垃圾收集,但並不是說數據存儲到方法區就常駐存在,這區域的內存回收目標主要是針對常量池的回收和堆類型的卸載
  • 方法區的內存不足的時候回觸發OutOfMemoryError異常。

運行常量池

  • Java編譯的class文件中 除了有類的版本、字段、方法、接口等描述信息外,還存放着常量池,用於存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池中存放。
  • 運行時常量池相對於Class文件常量池的另外一個重要特徵是具備動態性,Java語言並不要求常量一定要在編譯期才能產生,也就是並非預置入Class文件中常量池的內容才能進入方法區的運行時常量池,運行期間也可能將新的常量放入到常量池中,這種特性被開發人員利用得比較多的是String類的intern()方法。
  • 當常量池無法再申請到內存時會拋出OutOfMeMoryError異常

併發編程下的Java內存模型

  • 線程之間的共享變量存儲在主內存(main memory)中,每個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其他的硬件和編譯器優化
    多線程下的讀寫模型
    A線程與B線程之間進行通信時,線程A首先把修改過的共享變量更新到堆內存中,線程B再去讀取A更新過的共享變量,隨之而來的是兩個問題:
    1. 共享變量對各個線程的內存可見性
    2. 共享變量的競爭問題

內存可見性原則

當A線程對共享變量做出了修改,但是並沒有來得及將修改flush到共享內存中時,B線程讀取的共享變量並不是我們希望的預期值,這樣就產生了線程競爭,因此我們需要確保A的寫操作在任何時候都是對B線程可見的,這就引入了一個Happen-Before 規則

  • Java **volatile**會保證共享變量對內存的可見性

Happen-Before 規則

  • 程序順序規則:一個線程中的每個操作,happens- before 於該線程中的任意後續操作
  • 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 於隨後對這個監視器鎖的加鎖。
  • volatile變量規則:對一個volatile域的寫,happens- before 於任意後續對這個volatile域的讀。
  • 傳遞性:如果Ahappens- before B,且B happens- before C,那麼A happens- before C。

線程競爭的避免

  • 線程鎖:當前線程在進行數據操作的時候給該操作 添加鎖(ReentrantLock),其他線程只能阻塞在該線程操作數據結束後解鎖再進行操作
  • 線程同步: 每個java對象都有一個內部鎖,使用**synchronized**關鍵字進行方法聲明,這個方法在被一個線程操作時就會自動被上鎖,其效果與創建Lock對象對數據操作加鎖相似

引申

  1. Java程序的內存劃分和垃圾回收算法:堆內存(Heap Space)和永久代(Permanent Generation->Permgen)
  2. 爲什麼wait,notify和notifyall定義在object中
    https://blog.csdn.net/weixin_41950473/article/details/91592261
  3. synchronized關鍵字的作用
    https://blog.csdn.net/weixin_41950473/article/details/90049998
  4. volatile是如何保證內存可見性和防止編譯器指令性重排的?
  5. Java鎖的類型、原理及其用法
  6. Jvm 內存OutOfMemoryError場景與分析
  7. CAS原理入門與掃盲
  8. Java基本數據類型的存放位置
    基本數據類型是放在棧中還是放在堆中,這取決於基本類型聲明的位置。
    1. 方法中聲明的變量爲局部變量,局部變量存儲在虛擬機棧空間的棧幀中,隨着方法的被調用而創建,隨着棧的銷燬而銷燬,如果基本類型是新聲明的,存儲在棧中;如果基本類型聲明的是引用,該引用的基本類型實際存儲在堆中,棧中只是存儲對基本類型的引用
    2. 類中聲明的變量即成員變量,是全局變量,這個是放在堆中的,無論是新聲明的還是聲明的是引用,基本數據類型都是存貯在堆中的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章