JMM與JVM區別與聯繫(精煉總結)

在多線程編程中,就涉及到線程之間的通信。爲了更好的實現程序的高併發、高性能、高可用,就不得不知道JMM。至於高可用,今後再詳細總結。另外,此處有一文介紹HA,參考https://www.linuxprobe.com/high-availability.html


一、JMM

1、JMM定義了Java 虛擬機(JVM)在計算機內存(RAM)中的工作方式。JVM是java整個計算虛擬模型。

2、從抽象的角度來看,JMM定義了線程主內存之間的抽象關係:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。

3、本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其他的硬件和編譯器優化。

二、JVM對Java內存模型的實現

根據JMM模型,在JVM把內存分成了兩部分:線程棧區堆區

JVM中運行的每個線程都擁有自己的線程棧(也稱調用棧),線程棧包含了當前線程執行的方法調用相關信息。隨着代碼的不斷執行,調用棧會不斷變化。


1、線程棧區(Thread stack)

1)線程棧(thread stack,也叫線程堆棧),所擁有的資源是獨自的,對其他線程不可見。

2)線程棧,也包含正在的所有局部變量。

3)由當前線程創建的局部變量,對於非創建它的其他所有線程都是不可見的。

4)即使兩個線程正在執行完全相同的代碼,兩個線程仍然會在每個線程堆棧中創建該代碼的局部變量。即便可傳遞變量副本,但不共享原始局部變量本身。

2、堆區(Heap)

1)堆包含創建的java應用程序對象。

2)堆中的對象可以被具有對象引用的所有線程訪問。當一個線程訪問一個對象時,它也可以訪問該對象的成員變量。

3)如果兩個線程同時調用同一個對象上的一個方法,它們都可以訪問該對象的成員變量,但每個線程都有自己的局部變量副本

4)堆中的數據是共享的,線程不安全的

詳細說明:

  • 所有原始類型(boolean,byte,short,char,int,long,float,double)的局部變量都直接保存在線程棧當中,對於它們的值各個線程之間都是獨立的。對於原始類型的局部變量,一個線程可以傳遞一個變量副本給另一個線程,但原始變量是不共享的。
  • 堆區包含了Java應用創建的所有對象信息(包括原始類型的封裝類),不管對象是哪個線程創建的,不管對象是屬於一個成員變量還是方法中的局部變量,它都會被存儲在堆區。
  • 一個局部變量如果是原始類型,那麼它會被完全存儲到棧區。 一個局部變量也有可能是一個對象的引用,這種情況下,這個本地引用會被存儲到棧中,但是對象本身仍然存儲在堆區。
  • 對於一個對象的成員方法,這些方法中包含局部變量,仍需要存儲在棧區,即使它們所屬的對象在堆區。
  • 對於一個對象的成員變量,不管它是原始類型還是包裝類型,都會被存儲到堆區。
  • Static類型的變量以及類本身相關信息都會隨着類本身存儲在堆區。


基於JMM的JVM模型,既然堆中的數據是共享的,那麼在多線程環境中,就可能存在數據安全性問題。主要涉及到:可見性問題,競爭性問題等。

在此之前,回顧幾個點

A、計算機常識:

  • cpu執行的操作是原子性的,是不可拆分的。

B、造成數據安全性問題的必要條件

  • 多線程環境
  • 多個線程操作共享數據
  • 操作共享數據的語句不是原子性的(多條)

此中,引申的。後續補充


3、共享對象的可見性(事例說明)

如果兩個或多個線程共享一個對象,但沒有正確使用volatile聲明或Synchronized同步機制,一個線程更新了共享變量值後,對於其他線程來講是不可見的。如線程A,線程B同時要進行modify。

public class Account {

  private float balance;

  public void modify (float difference) {
    float value=this.balance;
    this.balance=value+difference;
  }
}

首先,線程A和線程B在各自的thread stack中維護了一分局部變量的副本,線程A修改了線程A中Thread stack中的局部變量,但是還沒有還沒將修改的數據刷新到Main Memory中,而線程B獲取的值依然是old value,就會出現問題

解決方案

  • 使用volatile關鍵字
  • 使用synchronized同步機制

tips:volatile與synchronized的區別:

  • volatile本質是在告訴jvm當前變量在寄存器中的值是不確定的,需要從主存中讀取;synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住
  • volatile僅修飾變量;synchronized則可以修飾變量、方法、代碼塊
  • volatile僅保證可見性;synchronized則可以保證可見性和原子性
  • volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞
  • volatile修飾的變量會禁止指令重排序,因而程序不會被編譯器優化;synchronized修飾的變量沒有禁止指令重排序,因而程序可以被編譯器優化

三、JVM

  • 程序計數器(PC)
  • java虛擬機棧
  • 本地方法棧
  • java堆
  • 方法區

1、程序計數器(PC)

是一塊很小的內存空間,用於記錄將要運行的指令。

tips:每個線程都需要一個程序計數器;各個線程的計數器相互獨立,是私有的。

2、java虛擬機棧

保存了局部變量、部分結果,並參與方法的調用和返回,的一個內存空間

tips:1)對各個線程來說,也是私有的;

         2)它和java線程同一時間創建;

         3)由java語言實現的

3、本地方法棧

與java虛擬機棧的功能相似

tips:1)java虛擬機棧用於管理Java函數的調用,本地方法棧用於管理本地方法的調用;

         2)由C語言實現的

4、java堆

tips:1)做存儲的,爲所有創建的對象和數組分配內存空間;

         2)被JVM中所有的線程共享

5、方法區

也被稱爲永久區,與堆空間相似,被JVM中所有的線程共享。

         1)方法區主要保存的信息是類的元數據,方法區中最爲重要的是類的類型信息、常量池、域信息、方法信息。其中運行時常量池就在方法區;

         2)GC對永久區的回收:一是對永久區常量池的回收;二是永久區對元數據的回收


參考鳴謝:

https://blog.csdn.net/u013887008/article/details/79681609 

https://www.jianshu.com/p/8a58d8335270

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章