JAVA內存模型與JVM內存結構

注意區分Java內存模型(Java Memory Model,簡稱JMM)與Jvm內存結構,前者與多線程相關,后者與JVM內部存儲相關。本文會對兩者進行簡單介紹。

一、JAVA內存模型(JMM)

1. 概念

說來話長,由于在不同硬件廠商和不同操作系統之間內存訪問有一定差異,所以會使得相同代碼在不同平臺上運行結果可能不一致。為了使java程序在各種平臺下達成一致的運行效果,所以JMM屏蔽掉各種硬件和操作系統的內存訪問差異。
JMM規定除局部變和方法參數以外的所有變量都存儲在主內存中。從線程角度,其基本工作方式是:工作內存保存了線程用到的變量和主內存的副本,只能修改工作內存的值然后刷回主存,不能直接讀寫主內存中的變量。

一般問到Java內存模型都是想問多線程,Java并發相關的問題。

2. 內存屏障

現代計算機CPU多為多核,每核有自己的高速緩存,易導致內存數據讀寫不一致,產生指令亂序和不可見性問題。內存屏障確保指令順序執行和內存操作的全局可見性,防止重排序,并即時更新和展示內存數據給其他CPU核,解決讀寫延遲問題。讀屏障清除緩存,確保后續讀取最新數據;寫屏障刷新緩存數據到內存,使其對其他核可見。JMM針對讀load寫store提出了針對這兩個操作的四種組合來覆蓋度讀寫的所有情況。
LoadLoad 屏障:確保所有之前的讀操作都完成后再執行之后的讀操作。
StoreStore 屏障:確保所有之前的寫操作都完成后再執行之后的寫操作。
LoadStore 屏障:確保所有之前的讀操作都完成后再執行之后的寫操作。
StoreLoad 屏障:確保所有之前的寫操作都完成并對其他處理器可見后,再執行之后的讀操作。

3.原子性 可見性 有序性

3.1原子性

原子性指的是一個操作是不可分割,不可中斷的,一個線程在執行時不會被其他線程干擾。i++不是原子操作,因為它是先讀取到i,再加1,是兩步操作不保證原子性。代表性的是synchronized關鍵字,該關鍵字修飾的方法或代碼塊可保證原子性。

3.2 可見性

可見性是指一個線程修改了某個變量的值,這個改動能立即被其他線程感知。volatile關鍵字可以保證變量的可見性,當變量被該關鍵字修飾時,這個變量的改動會被立即刷新到內存,其他線程會在主內存中讀取該變量的新值。final和synchronized也可保證可見性。
<happens-before>
happens-before是指前一個操作的結果對后續操作是可見的,并不是指前面一個操作一定發生在后面一個操作的前面。在不改變程序執行結果的前提下,編譯器和處理器可以自由優化程序執行順序,因為程序員只關心程序執行的語義是否正確。

3.3 有序性

在Java中,volatile和synchronized都能維護多線程操作的有序性。volatile通過內存屏障禁止指令重排,而synchronized則通過鎖定機制,確保同一時間只有一個線程可以執行被其保護的代碼塊,從而實現有序性。

4.?synchronezid?volatile關鍵字

4.1?synchronezid?
4.1.1 基本使用

synchronezid可以修飾方法、類和代碼塊。修飾實例方法鎖住的是對象,即對象鎖;修飾靜態方法鎖住的是類,即類鎖;修飾代碼塊,指定加鎖對象,對給定對象加鎖,也是對象鎖。
對象鎖可以有多個,new幾個對象就有幾個對象鎖,但是類鎖只有一把。

//修飾方法
public synchronized void add(){i++;
}
//修飾類
public static synchronized void add(){i++;
}
//修飾代碼塊
public void add() {synchronized (this) {i++;}
}
4.1.2 底層原理

查看上面代碼的字節碼

//修飾代碼塊
public void add();Code:0: aload_01: dup2: astore_13: monitorenter    // synchronized關鍵字的入口4: getstatic     #2                  // Field i:I7: iconst_18: iadd9: putstatic     #2                  // Field i:I12: aload_113: monitorexit  // synchronized關鍵字的出口14: goto          2217: astore_218: aload_119: monitorexit // synchronized關鍵字的出口20: aload_221: athrow22: return

通過字節碼文件看出synchronized修飾代碼塊使用monitorenter和monitorexit指令。monitorenter指令指向同步代碼塊的開始位置,monitorexit指令則指明同步代碼塊的結束位置。每個對象有一個監視器鎖(monitor)。當monitor被占用時就會處于鎖定狀態,線程執行monitorenter指令時嘗試獲取monitor的所有權,設置計數器值為1。執行monitorexit指令,將釋放 monitor(鎖)并設置計數器值為0。monitor存儲于對象頭信息中,每個對象都存在一個monitor與之關聯。

//修飾方法
public synchronized void add();descriptor: ()Vflags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field i:I5: iconst_16: iadd7: putfield      #2                  // Field i:I10: returnLineNumberTable:line 5: 0line 6: 10

synchronized修飾實例方法對應的字節碼沒有 monitorenter和monitorexit ,卻額外多了 ACC_SYNCHRONIZED。因為整個方法都是同步代碼,因此就不需要標記同步代碼的入口和出口了。當線程線程執行到這個方法時會判斷是否有這個ACC_SYNCHRONIZED標志,如果有的話則會嘗試獲取monitor對象鎖。如果有異常發生,線程自動釋放鎖。

4.2?volatile

能保證變量的可見性,禁止指令重排序。
可見性原理
每個線程都有一個Jvm棧,棧內保存線程運行時的變量信息。當線程訪問對象的屬性時,首先會找到堆內對象存的變量值,再將其保存為棧內的一個副本,之后會直接修改副本中屬性的值。修改完后不會立即將修改的值更新到堆中,這就導致某些線程讀取到的還是舊值。volatile就是當副本中屬性的值被修改后保證其能立即同步到堆中,從而其他線程讀取到該值,也是新的值。


禁止指令重排序原理
通過插入內存屏障禁止指令重排序。插入內存屏障,相當于告訴CPU和編譯器先于這個命令的必須先執行,后于這個命令的必須后執行。volatile寫操作的前面插入一個StoreStore屏障,后面插入一個SotreLoad屏障。


<volatile不能保證線程安全,可見性不能保證原子操作>
?

二、JVM內存結構

1. 組成

JVM的內存劃分為5部分,Java棧,本地方法棧,堆,程序計數器和方法區。
1-JAVA棧 即虛擬機棧
根據線程創建而創建,所以每個線程都有一個虛擬機棧。虛擬機棧存儲的是棧幀,每個棧幀對應一個方法,且都有自己的局部變量表,操作數棧、動態鏈接和返回地址等。
局部變量表存放了編譯器可知的各種基本數據類型(int、short、byte、char、double、float、long、boolean)、對象引用(reference類型,它不等同于對象本身,可能是一個指向對象起始地址的引用指針,也可能是指向一個代表對象的句柄或其他與此對象相關的位置)和returnAddress類型(指向了一跳字節碼指令的地址)。
JVM規范中,Java虛擬機棧部分規定了兩種異常:StackOverflowError發生在遞歸調用過深時,由于程序設計的錯誤,如遞歸無終止條件;OutOfMemoryError發生在JVM內存不足或設置過小,導致無法為新線程分配棧空間。
2-本地方法棧
java虛擬機棧為虛擬機執行Java方法服務。本地方法棧則為虛擬機使用的native方法服務。native方法是用C語言實現的底層方法。
3-堆
生命周期與進程相同,被所有線程所共享的內存區域。該區域存放的是對象實例。堆同時也是GC的主要區域。通常情況下,它占用的空間是所有內存區域中最大的,但如果無節制地創建大量對象,也容易消耗完所有的空間;堆的內存空間既可以固定大小,也可運行時動態地調整,通過參數-Xms設定初始值、-Xmx設定最大值。
4-程序計數器
它是一塊極小的內存空間。記錄了當前線程執行到的字節碼行號。每個線程都有自己的程序計數器,互不影響。native方法計數器為空。
5-方法區
被線程共享,儲存已被虛擬機加載的類信息、常量、靜態變量、jit編譯后的代碼等數據。

Java源代碼編譯成Java Class文件后通過類加載器ClassLoader加載到JVM中
類存放在方法區
類創建的對象存放在
堆中對象的調用方法時會使用到虛擬機棧,本地方法棧,程序計數器
方法執行時每行代碼由解釋器逐行執行
熱點代碼由JIT編譯器即時編譯
垃圾回收機制回收堆中資源
和操作系統打交道需要調用本地方法接口

2. 類加載過程

2.1 加載

加載指的是將類的class文件讀入到內存中,并為之創建一個java.lang.Class對象。 類加載階段可以使用系統提供的類加載器(ClassLoader)來完成,也可以使用用戶自定義的類加載器(繼承ClassLoader)完成。

2.2?連接

2.2.1 驗證

驗證被加載的類文件符合JVM規范,保證載入的類不會危害JVM。

文件格式驗證→元數據驗證→字節碼驗證→符號引用驗證

2.2.1.1 文件格式驗證

2.2.1.2 元數據驗證

2.2.1.3?字節碼驗證

2.2.1.4?符號引用驗證

2.2.2?準備

在方法區中為類變量(被static修飾的變量)分配內存,并將其初始化為默認值。

對于 public static int value = 123;變量value在準備階段過后的初始值為0而不是123,初始化時才會將value值賦為123。 如果類字段的字段屬性表中存在ConnstantValue屬性,那在準備階段value就會被初始化為ConstantValue屬性所指定的值,如:public static final int value = 123;編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值為123。

2.2.3?解析

將類中的符號引用轉化為直接引用。編譯的時候每個java類都會被編譯成一個class文件,但在編譯的時候虛擬機并不知道所引用類的地址,所以就用符號引用來代替。符號引用以一組符號來描述所引用的目標。直接引用可以是直接指向目標的指針。

2.3?初始化

執行類的初始化方法(<clinit>()方法)來初始化類的靜態變量(程序設置值)和執行靜態代碼塊。

2.4?使用

2.5?卸載

3. 類加載機制

1、全盤負責 類加載器加載某個類時,該類所依賴和引用其它的類也由該類加載器載入。

2、雙親委派 先讓父加載器加載該類,父加載器無法加載時才考慮自己加載。 如果父加載器還存在其父加載器,則進一步向上委托,如果父類加載器可以完成父加載任務,就成功返回,如果父加載器無法完成加載任務,子加載器才會嘗試自己去加載,可避免重復加載。

3、緩存機制 緩存機制保證所有加載過的class都會被緩存,當程序中需要某個類時,先從緩存區中搜索,如果不存在,才會讀取該類對應的二進制數據,并將其轉換成class對象,存入緩存區中。 這就是為什么修改了class后,必須重啟JVM,程序所做的修改才會生效的原因。

4. 反射

Java 的反射機制是指在運行狀態中,對于任意一個類都能夠知道這個類所有的屬性和方法; 并且對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成為Java語言的反射機制。

4.1 實例化方式

Date date=new Date();
//方式1
Class<?> date =Class.forName("java.util.Date");
//方式2
System.out.println(date.getClass());
//方式3   
System.out.println(Date.class);

4.2 實例化對象

//通過反射機制,獲取Class,通過Class來實例化對象
Class<?>  cl=Class.forName("java.util.Date");
//newInstance() 這個方法會調用Date這個類的無參數構造方法,完成對象的創建。
// 重點是:newInstance()調用的是無參構造,必須保證無參構造是存在的!
Object object=cl.newInstance();

5.GC

(待施工)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/719128.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/719128.shtml
英文地址,請注明出處:http://en.pswp.cn/news/719128.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

No matching version found for @babel/traverse@^7.24.0.

問題&#xff1a; npm安裝 依賴失敗&#xff0c;找不到所需依賴。 原因&#xff1a; npm鏡像源中沒有該依賴。&#xff08;大概率是因為依賴最近剛更新&#xff0c;當前鏡像源沒有同步&#xff09; 解決&#xff1a; 查看自己的npm鏡像&#xff1a;npm config get registry…

機器學習-面經(part2)

3. 驗證方式 3.1什么是過擬合?產生過擬合原因? 定義:指模型在訓練集上的效果很好,在測試集上的預測效果很差 數據有噪聲 訓練數據不足,有限的訓練數據 訓練模型過度導致模型非常復雜3.2 如何避免過擬合問題? 3.3 什么是機器學習的欠擬合?產生原…

D4890可應用在對講機上,采用 SOP8/MSOP8兩種封裝形式

D4890 目前客戶主要使用在對講機上&#xff0c;電壓范圍2.2V &#xff5e; 5.5V之間&#xff0c;輸出功率&#xff08;THDN1%&#xff09;1.0W/8Ω 5.0V。采用 SOP8/MSOP8兩種封裝形式。 2、推薦的應用線路圖如下&#xff1a; 3、實際測試輸出波形如下&#xff08;VCC4.5V&…

Web Component 轉圖片

一、HTML 轉圖片 目前&#xff0c;常見的開源的能夠將 HTML 轉換為圖片有html2canvas、dom-to-image&#xff0c;大部分場景下&#xff0c;這些開源庫都能很友好的處理。 HTML 轉圖片的實現原理&#xff0c;通常分為兩種&#xff1a;svg 與 canvas。今天主要討論下 svg 的場景…

Flutter中使用Dio庫封裝網絡請求服務工具類

在Flutter應用程序中&#xff0c;進行網絡請求是非常常見的任務。Dio是一個強大的、易于使用的Dart包&#xff0c;用于處理HTTP請求。本篇博客將介紹如何封裝Dio庫&#xff0c;以及如何在Flutter應用中進行網絡請求并取消請求。 什么是Dio&#xff1f; Dio是一個基于Dart語言…

解決android studio build Output中文亂碼

1.效果如下所示&#xff1a; 代碼運行報錯的時候&#xff0c;Build Output報的錯誤日志中中文部分出現亂碼&#xff0c;導致看不到到底報的什么錯。 2.解決辦法如下&#xff1a; 點擊Android studio開發工具欄的Help-Edit Custom VM Options....&#xff0c;Android studio會…

springboot微服務中集成了mybatis的服務引入了其他集成了mybatis的服務此時調用引入的服務中的某個mapper接口時報沒有注入

在啟動類上加引入的服務中的mapper路徑&#xff0c;在配置文件中將mapperLocations的值改為classpath*:mapper/.xml&#xff1a; MapperScan(basePackages {"com.ruoyi..mapper"}) 和 mapperLocations: classpath*:mapper/*.xml 是 MyBatis 在 Spring Boot 中配置 M…

AutoGPT實現原理

AutoGPT是一種利用GPT-4模型的自動化任務處理系統&#xff0c;其主要特點包括任務分配、多模型協作、互聯網訪問和文件讀寫能力以及上下文聯動記憶性。其核心思想是通過零樣本學習&#xff08;Zero Shot Learning&#xff09;讓GPT-4理解人類設定的角色和目標&#xff0c;并通過…

端口號被占用時的解決辦法

1、查看端口占用的進程號 netstat -ano |findstr 8080 2、 找到占用端口的程序 tasklist |findstr 2264 3、kill端口 taskkill /pid 2264 /f

文物預防性保護方案整體結構及軟件介紹

?文物預防性保護監測與調控系統整體是構架在商業級技術平臺上的多層綜合性應用,采用分布式部署的模塊化設計,以智能監測終端及高精傳感器為核心的感知系統。系統通過以下的層次結構協同工作完成全面的監控與調控功能&#xff1a; 1)系統依靠文物監測調控模型作為運行核心&…

基于springboot+vue的校園愛心捐贈互助管理系統(源碼+論文)

目錄 前言 一、功能設計 二、功能實現 三、庫表設計 四、論文 前言 隨著經濟水平和生活水平的提高在校大學生在校需要處理的物品也在不斷增加&#xff0c;同時校園內還存在很多貧困生&#xff0c;可以通過線上平臺實現資源的整合和二次利用&#xff0c;通過線上平臺求助信…

護眼燈有效果嗎怎么樣?推薦五款值得入手的護眼臺燈

隨著護眼臺燈被越來越多的人解鎖新的護眼攻略&#xff0c;它的產品熱度也越來越高&#xff0c;而且光線柔和&#xff0c;是一款非常不錯的照明用具。但是也有不少用戶反饋買到的護眼臺燈效果不好&#xff0c;有時候還會覺得刺眼&#xff0c;有些不合格的臺燈使用時間一久還會散…

動態IP代理技術在網絡爬蟲中的實際使用

目錄 一、動態IP代理技術概述 二、動態IP代理技術的優勢 三、動態IP代理技術的實際應用 四、注意事項 五、案例分析 六、結論 隨著互聯網的迅猛發展&#xff0c;網絡爬蟲成為了獲取信息、分析數據的重要工具。然而&#xff0c;在進行大規模爬取時&#xff0c;爬蟲常常面臨…

gin gorm學習筆記

代碼倉庫 https://gitee.com/zhupeng911/go-advanced.git https://gitee.com/zhupeng911/go-project.git 1. gin介紹 Gin 是使用純 Golang 語言實現的 HTTP Web框架&#xff0c;Gin接口設計簡潔&#xff0c;提供類似Martini的API&#xff0c;性能極高&#xff0c;現在被廣泛使用…

《C語言缺陷和陷阱》-筆記

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言一、pandas是什么&#xff1f;二、使用步驟 1.引入庫2.讀入數據總結 前言 在這一節中&#xff0c;我們將探索對記號的意義的普遍的誤解以及記號和組成它們的字…

指針習題二

使用函數指針實現轉移表 #include <stdio.h> int add(int a, int b) {return a b; } int sub(int a, int b) {return a - b; } int mul(int a, int b) {return a * b; } int div(int a, int b) {return a / b; } int main() {int x, y;int input 1;int ret 0;int(*p[…

學習python時一些筆記

1、winr 命令提示符的快捷鍵 輸入cmd進入終端 2、在終端運行桌面上的python文件 cd desktop(桌面) cd是進入該文件夾的意思。 cd .. 回到上一級 運行python時一定要找到文件的所在地 輸入python進入&#xff0c;exit()退出%s字符串占位符%d數字占位符%f浮點數占位符input輸…

Linux速覽(1)——基礎指令篇

在上一章對Linux有了一些基礎了解之后&#xff0c;本章我們來學習一下Linux系統下一些基本操作的常用的基礎指令。 目錄 1. ls 指令 2. pwd&&whoami命令 3. cd 指令 4. touch指令 5.mkdir指令&#xff08;重要&#xff09;&#xff1a; 6.rmdir指令 && …

vue面試題:如何保存頁面的當前的狀態?

如何保存頁面的當前的狀態&#xff1f; 既然是要保持頁面的狀態&#xff08;其實也就是組件的狀態&#xff09;&#xff0c;那么會出現以下兩種情況&#xff1a;組件會被卸載&#xff1a;&#xff08;1&#xff09;將狀態存儲在LocalStorage / SessionStorage優點&#xff1a;缺…

帶大家做一個,易上手的水煮牛肉

今天帶大家做川菜系中的 水煮牛肉 這個菜是比較費辣椒的 制作成本相對一般菜來說 會高一些 一塊牛肉 泡水劃冰 從超時買的干腐竹 切成小片 溫水浸泡五分鐘 泡軟它 然后撈出來 去干水分 牛肉切片 盡量切薄一點 三瓣左右蒜 一塊生姜 去皮切末 牛肉中下入 一個雞蛋 小半勺…