筆記07 - Java內存模型

2021-02-14 碼農幫派

線程是CPU調度的最小單元,線程中的字節碼最終是放到CPU中執行的,CPU執行的時候伴隨著數據的讀寫,在Java中所有的數據都是放在主內存(RAM)中的,這一過程如下所示:

隨著CPU技術的發展,CPU的執行速度越來越快,但是內存技術並沒有太大的改變,這就導致內存中數據的讀寫速度和CPU處理數據的速度差距越來越大,CPU需要較長時間等待內存的讀寫,這就意味著CPU會出現空轉的情況。為了解決性能的瓶頸,進一步釋放CPU的運算能力,在CPU中添加了高速緩存(cache)作為數據的緩衝。

在執行任務之前,CPU會首先將數據從主內存中複製到高速緩存中,讓運算能夠快速進行,當運算完成之後,再將緩存中的結果刷回到主內存中,這樣CPU就不用等待主內存中數據的讀寫了。

目前市面上有的手機有多個CPU、一些CPU還有多核,每個CPU都可以運行一個線程,這就意味著主內存中的數據同時可能被多個線程同時讀寫,而CPU的高速緩存也是相互獨立的,這就會導致主內存中數據的不一致的問題。

另外來自於硬體的指令重排也會導致數據的不一致。CPU內部的運算單元為了儘量被充分利用,處理器會對字節碼進行指令重排。

比如下面的代碼(b的賦值不影響a的運算):

編譯之後的字節碼為:

上面的指令中可以看到,指令7(對應最後a + 1)並不影響指令2和指令3,這種情況下,CPU會對指令的順序進行調整:

從Java語言的角度,調整後的代碼順序:

上面的指令重排在多線程的情況下,由於指令的重拍,單個線程內並沒有影響,但可能影響多線程中數據的讀寫操作,從而導致一些意想不到的結果。

Java的內存模型

如果任由CPU進行優化或多線程的操作,會導致Java程序運行結果出乎意料,為了解決這種問題,JVM推出了Java內存模型來解決。

在java內存模型中,我們統一使用工作內存(working memory)來當作CPU中的寄存器或高速緩存的抽象。線程之間的共享變量存儲在主內存(main memory)中,每個線程有自己的工作內存,線程的工作內存中存儲了共享內存中變量的副本。

在JMM規範中,又一個重要的規則happens-before。

happens-before先行發生原則

happens-before用於描述兩個操作在內存中的可見性,通過保證數據的可見性,從而讓應用程式免於數據競爭的幹擾。

會發生指令重排的情況:

下面的代碼:

int a = 10;  // 1b = b + 1;   // 2

由於操作2和操作1之間並不會相互影響,這種情況下CPU為了提高計算單元的利用率,一般會進行指令重排。

但是我們要是把代碼改成下面這種:

int a = 10;  // 1b = b + a;   // 2

由於操作2依賴操作1的執行,這種情況下就不會發生指令重排了。

在Java內存模型(JMM)中,有以下的一些情況會自動符合happens-before規則:

1. 程序次序規則

在單線程中,一段代碼中邏輯順序靠前的字節碼一定是對後續邏輯字節碼可見的。

2. 鎖定規則

無論是單線程還是多線程環境中,一個鎖處於鎖定狀態,那麼必須首先執行unlock才做,這個所才能被其他的線程獲得並重新lock。

3. 變量可見規則

volatile關鍵字保證了變量的可見性。如果一個線程寫了volatile的變量,另外一個線程讀取這個變量,那麼這個寫操作一定是happens-before讀操作的。

4. 線程啟動規則

Thread對象的start方法先行發生於此線程的每一個動作。假設線程A在執行的過程中通過執行ThreadB.start()來啟動線程B,那麼線程A中對共享變量的修改,在線程B開始執行後對線程B可見。

5. 線程終結規則

假如線程A在執行的過程中,通過調用ThreadB.join()方法等待線程B終止,那麼線程B在終止之前對共享變量的修改在線程A等到返回之後可見。

6. 對象終結規則

一個對象的初始化完成發生在它的finalize()方法之前,也就是對象初始化的數據對它的finalize方法可見。

兩種happens-before化的方式

1. 使用volatile關鍵字

2. 使用synchronized關鍵字

通過上面兩種方式,在一個線程中調用setValue設置的value對其他的線程可見,再setValue之後,其他的線程調用getValue獲取到的value一定是1.

相關焦點

  • Java內存模型與volatile關鍵字
    Java的內存模型大概樣子還是有必要了解下的,今天就學習了下,順便學習了一點volatile關鍵字!Java內存模型主內存中存儲一些可以共享的變量比如實例欄位、靜態欄位和構成數組對象的元素,但是不包括局部變量與方法參數,因為它們是線程私有的,不會被共享。
  • java線程前傳——jvm內存結構、內存模型和cpu結構
    ,這個過程是少不了的一個線程肯定是要運行在一個核上的,多個線程可以運行在不同的核上,這個時候,因為緩存的存在,如果沒有同步機制,那一個線程修改了緩存的數據,另一個線程也修改了緩存的數據,這個時候這兩個線程修改後的數據都需要寫入到內存當中,就會出現問題jvm為了方便,將這些緩存抽象出來,構造了自己的內存模型,即主內存和工作內存的數據交互,即java 內存模型(jmm)
  • 簡述Java內存模型
    所以我希望如果本章節出現讀者第一次接觸的名詞以及很難理解的概念時,儘量多讀幾遍,把這些概念聯想出模型來記憶,必要時反覆翻閱。2.3.1什麼是Java內存模型在介紹Java內存模型(JMM)前,我要打消讀者一個錯誤的認知,那就是JMM與JVM到底是什麼關係,現在告訴大家,Java虛擬機模型(JVM)與Java內存模型(JMM)沒有本質上的聯繫。
  • 淺析java內存管理機制
    不同的程式語言有不同的內存管理機制,本文在對比C++和Java語言內存管理機制的不同的基礎上,淺析java中的內存分配和內存回收機制,包括java對象初始化及其內存分配,內存回收方法及其注意事項等……中,內存管理由JVM完全負責,java中的「垃圾回收器」負責自動回收無用對象佔據的內存資源,這樣可以大大減少程序猿在內存管理上花費的時間,可以更集中於業務邏輯和具體功能實現;但這並不是說java有了垃圾回收器程序猿就可以高枕無憂,將內存管理拋之腦外了!
  • 談談Java內存管理
    來自:後端技術雜談 _ 颯然Hang作者:颯然Hang連結:http://www.rowkey.me/blog/2016/05/07但是在寫程序的過程中卻也往往因為這樣而造成了一些不容易察覺到的內存問題,並且在內存問題出現的時候,也不能很快的定位並解決。因此,了解並掌握Java的內存管理是一個合格的Java程式設計師必需的技能,也只有這樣才能寫出更好的程序,更好地優化程序的性能。
  • Java內存模型原理,你真的理解嗎?
    這篇文章主要介紹模型產生的問題背景,解決的問題,處理思路,相關實現規則,環環相扣,希望讀者看完這篇文章後能對 Java 內存模型體系產生一個相對清晰的理解,知其然知其所以然。在介紹 Java 內存模型之前,我們先了解一下物理計算機中的並發問題,理解這些問題可以搞清楚內存模型產生的背景。
  • [C++學習筆記]內存模型常見問題梳理
    C++內存模型常見問題梳理最近在學習C++,於是打算整理一些專題知識,梳理自己的學習成果。一來是回憶和整合,加深理解,再一個是希望大佬指點。目錄背景實習和學習需要。內存模型是一個經常會被問到的問題,加上語言本身比較複雜,經常日常使用往往不會在意,所以特此探究一下。正文編譯過程C++的程序從原始碼開始,如何變成一個可執行的程序呢?
  • jvm面試系列一:java內存模型你掌握了多少?
    今天我們就來聊一聊Java內存模型,面試中面試官會通過考察你對jvm的理解更深入得了解你的水平。
  • Java堆外內存排查小結
    通過本文,你應該了解:pmap 命令gdb 命令perf 命令內存 RSS、VSZ的區別java NMT起因這幾天遇到一個比較奇怪的問題,覺得有必要和大家分享一下。我們的一個服務,運行在docker上,在某個版本之後,佔用的內存開始增長,直到docker分配的內存上限,但是並不會OOM。
  • JAVA學習筆記1/4
    記錄個人在網上看視頻寫下來的筆記,視頻一個400多個(平均一個視頻6分鐘),這裡大概是100個多些的視頻所做的筆記,這只是入門筆記的1/
  • 面試中常會問到的Java內存模型原理,看了這篇秒懂
    這篇文章主要介紹模型產生的問題背景,解決的問題,處理思路,相關實現規則,環環相扣,希望讀者看完這篇文章後能對 Java 內存模型體系產生一個相對清晰的理解,知其然知其所以然。內存模型產生背景在介紹 Java 內存模型之前,我們先了解一下物理計算機中的並發問題,理解這些問題可以搞清楚內存模型產生的背景。
  • java創建對象的過程詳解(從內存角度分析)
    java對象的創建操作其實我在《JVM系列之類的加載機制》一文曾經提到過,包含兩個過程:類的初始化和實例化。為此為了理解的深入,我們還需要再來看一下類的生命周期。一張圖表示:從上面我們可以看到,對象的創建其實包含了初始化和使用兩個階段。有了這個印象之後,我們就能開始今天的文章了。
  • 求你了,再問你Java內存模型的時候別再給我講堆棧方法區了…
    經常,我都在繼續追問了一些他們回答的"Java內存模型"相關的知識後,友善的提醒一句,其實我想問的Java內存模型並不是他回答的這個…有的時候,我會進一步提醒一句:是和並發編程有關的,是和主內存以及線程工作內存有關的。。。那麼,本文就來簡單說一說,關於Java內存模型,到底應該如何回答這個面試題。
  • Java內存分配和String類型的深度解析
    一、引題在java語言的所有數據類型中,String類型是比較特殊的一種類型,同時也是面試的時候經常被問到的一個知識點,本文結合java內存分配深度分析關於String的許多令人迷惑的問題。下面是本文將要涉及到的一些問題,如果讀者對這些問題都了如指掌,則可忽略此文。1、java內存具體指哪塊內存?這塊內存區域為什麼要進行劃分?是如何劃分的?
  • Netty 內存模型分析(一)ByteBuf總覽
    具體 ByteBuf 又分為堆內存 Heap 和 直接內存 Direct。由於netty是對java nio的封裝,所以最終底層通信仍然是使用 ByteBuffer 進行,所以就是將array數組轉化為 ByteBuffer ,從 internalNioBuffer 可見一斑。
  • JAVA架構師必備詞彙和知識點
    >03 分布式框架Dubbo、Motan、Spring-Could04 資料庫中間件DRDS 、Mycat、360 Atlas、Cobar (不維護了)05 消息隊列RabbitMQ、ZeroMQ、Redis、ActiveMQ、Kafka06 註冊中心Zookeeper、Redis07
  • java內存溢出問題分析過程二(附MAT超全操作文檔)
    前言java程序的性能問題定位,一直都是開發者需要面對的一個「攔路虎」, 在前面的兩篇文章中,已經介紹了Heap dump的概念和生成方式,以及Shallow heap和Retained heap以及GC ROOT的概念,本篇文章,我們繼續來介紹一些新的概念和基於一個dump案例,詳盡的介紹,該程序OOM後,改如何去定位具體原因
  • JVM內存區域之線程私有區域
    對於java程式設計師來說,在虛擬機自動內存管理機制的幫助下,不再需要為沒一個new操作去配對的free/delete(C、C++語言對對象的刪除和內存釋放操作),不容易出現內存洩漏和內存溢出問題,看起來由虛擬機管理內存一切看起來很美好。
  • 大型企業JVM性能調優實戰Java垃圾收集器及gcroot
    from C:\ProgramFiles\Java\jdk1.8.0_211\jre\lib\rt.jar][Loaded java.lang.Shutdown$Lock from C:\ProgramFiles\Java\jdk1.8.0_211\jre\lib\rt.jar]小結10:類加載器-雙親委派模型目標掌握和了解類加載器的雙親委派模型
  • 最全的Java 運算符教程筆記
    複製並粘貼下面的 Java 程序並保存為 Test.java 文件,然後編譯並運行這個程序:01.public class Test {02. 複製並粘貼下面的Java程序並保存為Test.java文件,然後編譯並運行這個程序:Test.java 文件代碼:01.public class Test {02.