求你了,再問你Java內存模型的時候別再給我講堆棧方法區了…

2021-02-20 Linkoffer

點擊上方「linkoffer」,

選擇關注公眾號高薪職位第一時間送達

最近,面試過很多Java中高級開發,問過很多次關於Java內存模型的知識,問完之後,很多人上來就開始回答:

Java內存模型由幾部分組成,堆、本地方法棧、虛擬機棧、方法區…

每一次我不想打斷他們的話,雖然我知道這又是一個誤會了我的問題的朋友。

其實,我想問的Java內存模型,是和並發編程有關的。而候選人給我回答的那叫JVM內存結構,完全是兩回事。

很多時候,在我沒有打斷他們的情況下,一部分人慢慢的講到了GC相關的知識。這種情況下,我只能硬著頭皮繼續問一些和JVM有關的知識。

但是,我的本意其實是想看一下他對Java並發有多少了解啊。

經常,我都在繼續追問了一些他們回答的"Java內存模型"相關的知識後,友善的提醒一句,其實我想問的Java內存模型並不是他回答的這個…

有的時候,我會進一步提醒一句:是和並發編程有關的,是和主內存以及線程工作內存有關的。。。

那麼,本文就來簡單說一說,關於Java內存模型,到底應該如何回答這個面試題。

首先,我們先來分析一下問什麼很多人,甚至是大多數人會答非所問呢?

我覺得主要有幾個原因:

1、Java內存模型,這個詞聽著太像是關於內存分布的知識了。聽上去和並發編程沒有半毛錢關係。

2、網上很多資料都是錯的。不信你去網上搜索一下"Java內存模型",你會發現,很多人打著內存模型的標題,介紹了JVM內存結構的知識。

這裡提一句,我嘗試著Google搜索了一下搜索"Java內存模型",首頁展示結果如下:

首頁排名靠前的5篇文章中,有1篇是錯的,介紹了JVM內存結構。

值得慶幸的的是,首頁前5篇文章中,有兩篇是我寫的,至少我的這兩篇我敢確定是不具備任何誤導性的!!

3、還存在一種情況,雖然不多見,但是也有。那就是很多面試官自己也以為內存模型就是要介紹堆、棧、方法區這些知識。就導致有時候面試者不知道自己到底應該如何回答。

那麼,到底什麼是Java內存模型?關於這道面試題應該如何回答呢?

我曾經在《再有人問你Java內存模型是什麼,就把這篇文章發給他》中詳細的介紹過Java內存模型的來龍去脈,這裡再重新回顧一下。

Java內存模型是根據英文Java Memory Model(JMM)翻譯過來的。其實JMM並不像JVM內存結構一樣是真實存在的。他只是一個抽象的概念。

Java內存模型的相關知識在 JSR-133: Java Memory Model and Thread Specification 中描述的。JMM是和多線程相關的,他描述了一組規則或規範,這個規範定義了一個線程對共享變量的寫入時對另一個線程是可見的。

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中提供了一系列和並發處理相關的關鍵字,比如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是比較影響性能的,雖然編譯器提供了很多鎖優化技術,但是也不建議過度使用。

前面我介紹完了一些和Java內存模型有關的基礎知識,只是基礎,並不是全部,因為隨便一個知識點還是都可以展開的,如volatile是如何實現可見性的?synchronized是如何實現有序性的?

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

首先,先和面試官確認一下:您說的內存模型指的是JMM,也就是和並發編程有關的那一個吧?

在得到肯定答覆後,再開始介紹(如果不是,那可能就要回答堆、棧、方法區哪些了….囧…):

Java內存模型,其實是保證了Java程序在各種平臺下對內存的訪問都能夠得到一致效果的機制及規範。目的是解決由於多線程通過共享內存進行通信時,存在的原子性、可見性(緩存一致性)以及有序性問題。

除此之外,Java內存模型還提供了一系列原語,封裝了底層實現後,供開發者直接使用。如我們常用的一些關鍵字:synchronized、volatile以及並發包等。

回答到這裡就可以了,然後面試官可能會繼續追問,然後根據他的追問再繼續往下回答即可。

所以,當有人再問你Java內存模型的時候,不要一張嘴就直接回答堆棧、方法區甚至GC了,那樣顯得很不專業!

相關焦點

  • java線程前傳——jvm內存結構、內存模型和cpu結構
    ,這個過程是少不了的一個線程肯定是要運行在一個核上的,多個線程可以運行在不同的核上,這個時候,因為緩存的存在,如果沒有同步機制,那一個線程修改了緩存的數據,另一個線程也修改了緩存的數據,這個時候這兩個線程修改後的數據都需要寫入到內存當中,就會出現問題jvm為了方便,將這些緩存抽象出來,構造了自己的內存模型,即主內存和工作內存的數據交互,即java 內存模型(jmm)
  • 淺析java內存管理機制
    Java 堆中生成一個代表這個類的 java.lang.Class 對象,作為對方法區中這些數據的訪問入口。CPU緩存層的執行速度,遠大於在主存上的執行速度3、Java內存模型中的堆棧分布在硬體內存結構中的CPU寄存器,CPU緩存層,CPU主存中,大部分分布在主存中
  • Java內存分配和String類型的深度解析
    一、引題在java語言的所有數據類型中,String類型是比較特殊的一種類型,同時也是面試的時候經常被問到的一個知識點,本文結合java內存分配深度分析關於String的許多令人迷惑的問題。下面是本文將要涉及到的一些問題,如果讀者對這些問題都了如指掌,則可忽略此文。1、java內存具體指哪塊內存?這塊內存區域為什麼要進行劃分?是如何劃分的?
  • java創建對象的過程詳解(從內存角度分析)
    java對象的創建操作其實我在《JVM系列之類的加載機制》一文曾經提到過,包含兩個過程:類的初始化和實例化。為此為了理解的深入,我們還需要再來看一下類的生命周期。一張圖表示:從上面我們可以看到,對象的創建其實包含了初始化和使用兩個階段。有了這個印象之後,我們就能開始今天的文章了。
  • JVM中的五大內存區域劃分詳解及快速掃盲
    簡單用一張圖來理解這三個的關係:3. jvm的組成成分不了解jvm的同學看到這張圖後可能會有點懵逼,不過沒關係,放這張圖只是想讓你了解jvm中有三塊內容非常重要,1.java代碼如何執行?運行時數據區域組成虛擬機在執行java程序時,會將自己管理的內存劃分為幾個區域,每個區域都有自己的用途,並且創建時間和銷毀時間也不一樣。在程序運行時的內存區域主要可以劃分為五個,分別是:方法區、堆、虛擬機棧、本地方法棧、程序計數器。可以用下面的圖來描述:2.
  • 談談Java內存管理
    但是在寫程序的過程中卻也往往因為這樣而造成了一些不容易察覺到的內存問題,並且在內存問題出現的時候,也不能很快的定位並解決。因此,了解並掌握Java的內存管理是一個合格的Java程式設計師必需的技能,也只有這樣才能寫出更好的程序,更好地優化程序的性能。
  • 簡述Java內存模型
    所以我希望如果本章節出現讀者第一次接觸的名詞以及很難理解的概念時,儘量多讀幾遍,把這些概念聯想出模型來記憶,必要時反覆翻閱。2.3.1什麼是Java內存模型在介紹Java內存模型(JMM)前,我要打消讀者一個錯誤的認知,那就是JMM與JVM到底是什麼關係,現在告訴大家,Java虛擬機模型(JVM)與Java內存模型(JMM)沒有本質上的聯繫。
  • 面試官:你知道java類是怎麼跑起來的嗎?問的我一臉懵
    該Class對象作為方法區這個類的各種數據的訪問入口完成後,虛擬機外部的二進位字節流就按照虛擬機所需格式儲存在方法區中。這裡稍微理解一下對象和類的概念,對象是實例化的類。類的信息是存儲在方法區中的,對象是存儲在Java堆中的。類是對象的模板,對象是類的實例。
  • 筆記07 - Java內存模型
    線程是CPU調度的最小單元,線程中的字節碼最終是放到CPU中執行的,CPU執行的時候伴隨著數據的讀寫,在Java中所有的數據都是放在主內存(RAM
  • JVM(堆、方法區、棧、這些基礎您知道了嗎)
    方法區(Method Area-線程共享)1、介紹之前Hotspot方法區的實現是永久代(PermGen space),JRokit的實現是元空間(Metaspace),Oracle公司收購JRokit之後將Hotspot與JRokit合二為一JDK1.7
  • Java內存模型與volatile關鍵字
    Java的內存模型大概樣子還是有必要了解下的,今天就學習了下,順便學習了一點volatile關鍵字!Java內存模型主內存中存儲一些可以共享的變量比如實例欄位、靜態欄位和構成數組對象的元素,但是不包括局部變量與方法參數,因為它們是線程私有的,不會被共享。
  • 你見過老外的 Java 面試題嗎 (上)?
    畢竟不是專業的後臺開發,所以我在面試到後臺知識的時候果斷的退了出來,才讓自己免受了侮辱。不過鑑於我手速出眾,飛速的記錄下了 Java 的基礎題,所以準備貢獻出來,供大家享樂。當然問題是老外問的,答案是我編的。正文1、Java 中有哪些可用的分配內存Java 虛擬機在執行程序時候會將內存劃分為不同的數據區域方法區(Method Area)與 Java 堆一樣,是所有線程共享的內存區域。
  • 別翻了,這篇文章絕對讓你深刻理解java類的加載以及ClassLoader源碼分析
    點進文章的盆友不如先來做一道非常常見的面試題,如果你能做出來,可能你早已掌握並理解了java的類加載機制,若結果出乎你的意料,那就很有必要來了解了解java的類加載機制了。我為什麼要用這些面試題作為這篇文章的一部分?因為關於學習有一定的方法,你可以設想一下,如果博主不涉及並分析這幾個面試題,你還有耐心看到這裡嗎?小白槓精童鞋說有。。。好的,就算有,大篇大篇的理論各位扣心自問,能掌握所有知識嗎?小白槓精童鞋說說能。。。額,就算能,那你能保證光記理論一個月不遺忘嗎?小白槓精童鞋說可以。。。我特麼一老北京布鞋過去頭給你打歪(我這暴脾氣我天)。
  • JVM-概述和內存區域
    方法區(永久代)在jdk8中又叫做元空間Metaspace運行時數據區概述堆內存:保存所有引用數據的真實信息;棧內存:基本類型、運算、指向堆內存的指針;方法區:所以定義的方法的信息都保存方法區中,屬於共享區
  • 談談java的棧和堆
    先用一張圖展示一下Java堆棧的概況堆方法區:class文件信息,運行時常量池,以及編譯器編譯後的代碼堆:存儲對象,對象包含類的信息,指向方法區>棧虛擬機棧:表示Java方法執行的內存模型,每調用一個方法就會為每個方法生成一個棧幀(Stack Frame),用來存儲局部變量表、操作數棧、動態連結、方法出口等信息。
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。 在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口 加載階段完成後,這些二進位字節流按照虛擬機所需的格式存儲在方法區之中。
  • 這些Java字符串細節,能讓你百尺竿頭更進一步
    /   JVM內存結構   /剛才一直在說常量池,那麼常量池具體在哪呢?這就要來研究一下JVM的內存結構。JVM分為堆、棧、方法區,棧又分為本地方法棧和Java棧。,那麼恭喜你,前面的知識點你已基本掌握。
  • 深入分析Java中的關鍵字static
    然後在下一部分再來去分析static的原理,希望你能認真讀完。2、static關鍵字修飾類java裡面static一般用來修飾成員變量或函數。但有一種特殊用法是用static修飾內部類,普通類是不允許聲明為靜態的,只有內部類才可以。下面看看如何使用。
  • JVM內存分析,程式設計師進階的必經之路
    今天是劉小愛自學Java的第53天,學的有點懵…不過還是要感謝你的觀看,謝謝你。話不多說,開始今天的學習:JVM也就是Java虛擬機,它的內存結構這塊知識點。out,這個我還不太懂具體是什麼意思,但是.class文件就在這個文件夾裡面。如果不用開發工具,我們需要用javac編譯器將.java文件編譯成.class文件;開發工具等於是自動幫我們編譯了,非常的方便,但是原理我們要明白。
  • 你有真正理解 Java 的類加載機制嗎?|原力計劃
    JDK8移除了永久代,轉而使用元空間來實現方法區,創建的Class實例依舊在java heap(堆)中編寫一個新的java類時,JVM就會幫我們編譯成class對象,存放在同名的.class文件中。在運行時,當需要生成這個類的對象,JVM就會檢查此類是否已經裝載內存中。若是沒有裝載,則把.class文件裝入到內存中。