日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java内存区域(运行时数据区域)和内存模型(JMM)

發布時間:2023/12/3 java 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java内存区域(运行时数据区域)和内存模型(JMM) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文作者:czwbig
原文:https://www.cnblogs.com/czwbig/p/11127124.html
Java 內存區域和內存模型是不一樣的東西,內存區域是指 Jvm 運行時將數據分區域存儲,強調對內存空間的劃分

內存模型(Java Memory Model,簡稱 JMM )是定義了線程和主內存之間的抽象關系,即 JMM 定義了 JVM 在計算機內存(RAM)中的工作方式,如果我們要想深入了解Java并發編程,就要先理解好Java內存模型。

Java運行時數據區域
眾所周知,Java 虛擬機有自動內存管理機制,如果出現內存泄漏和溢出方面的問題,排查錯誤就必須要了解虛擬機是怎樣使用內存的。

下圖是 JDK8 之后的 JVM 內存布局。

JDK8 之前的內存區域圖如下:

在 HotSpot JVM 中,永久代中用于存放類和方法的元數據以及常量池,比如Class和Method。每當一個類初次被加載的時候,它的元數據都會放到永久代中。
永久代是有大小限制的,因此如果加載的類太多,很有可能導致永久代內存溢出,即萬惡的 java.lang.OutOfMemoryError: PermGen ,為此我們不得不對虛擬機做調優。

那么,Java 8 中 PermGen 為什么被移出 HotSpot JVM 了?我總結了兩個主要原因:

  • 由于 PermGen 內存經常會溢出,引發惱人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的開發者希望這一塊內存可以更靈活地被管理,不要再經常出現這樣的 OOM
  • 移除 PermGen 可以促進 HotSpot JVM 與 JRockit VM 的融合,因為 JRockit 沒有永久代。
    根據上面的各種原因,PermGen 最終被移除,方法區移至 Metaspace,字符串常量移至 Java Heap。
  • 引用自https://www.sczyh30.com/posts/Java/jvm-metaspace/

    程序計數器
    程序計數器(Program Counter Register)是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。

    由于 Java 虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器內核都只會執行一條線程中的指令。

    因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。

    如果線程正在執行的是一個 Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是 Native 方法,這個計數器值則為空(Undefined)。此內存區域是唯一一個在 Java 虛擬機規范中沒有規定任何 OutOfMemoryError 情況的區域。

    Java虛擬機棧
    與程序計數器一樣,Java 虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。

    虛擬機棧描述的是 Java 方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀(Stack Frame,是方法運行時的基礎數據結構)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。

    在活動線程中,只有位千棧頂的幀才是有效的,稱為當前棧幀。正在執行的方法稱為當前方法,棧幀是方法運行的基本結構。在執行引擎運行時,所有指令都只能針對當前棧幀進行操作。
    1. 局部變量表
    局部變量表是存放方法參數和局部變量的區域。 局部變量沒有準備階段, 必須顯式初始化。如果是非靜態方法,則在 index[0] 位置上存儲的是方法所屬對象的實例引用,一個引用變量占 4 個字節,隨后存儲的是參數和局部變量。字節碼指令中的 STORE 指令就是將操作棧中計算完成的局部變呈寫回局部變量表的存儲空間內。

    虛擬機棧規定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出 StackOverflowError 異常;如果虛擬機棧可以動態擴展(當前大部分的 Java 虛擬機都可動態擴展),如果擴展時無法申請到足夠的內存,就會拋出 OutOfMemoryError 異常。

    2. 操作棧
    操作棧是個初始狀態為空的桶式結構棧。在方法執行過程中, 會有各種指令往
    棧中寫入和提取信息。JVM 的執行引擎是基于棧的執行引擎, 其中的棧指的就是操
    作棧。字節碼指令集的定義都是基于棧類型的,棧的深度在方法元信息的 stack 屬性中。

    i++ 和 ++i 的區別:

    i++:從局部變量表取出 i 并壓入操作棧(load memory),然后對局部變量表中的 i 自增 1(add&store memory),將操作棧棧頂值取出使用,如此線程從操作棧讀到的是自增之前的值。
    ++i:先對局部變量表的 i 自增 1(load memory&add&store memory),然后取出并壓入操作棧(load memory),再將操作棧棧頂值取出使用,線程從操作棧讀到的是自增之后的值。
    之前之所以說 i++ 不是原子操作,即使使用 volatile 修飾也不是線程安全,就是因為,可能 i 被從局部變量表(內存)取出,壓入操作棧(寄存器),操作棧中自增,使用棧頂值更新局部變量表(寄存器更新寫入內存),其中分為 3 步,volatile 保證可見性,保證每次從局部變量表讀取的都是最新的值,但可能這 3 步可能被另一個線程的 3 步打斷,產生數據互相覆蓋問題,從而導致 i 的值比預期的小。

    3. 動態鏈接
    每個棧幀中包含一個在常量池中對當前方法的引用, 目的是支持方法調用過程的動態連接。

    4.方法返回地址
    方法執行時有兩種退出情況:

  • 正常退出,即正常執行到任何方法的返回字節碼指令,如 RETURN、IRETURN、ARETURN 等;
  • 異常退出。
  • 無論何種退出情況,都將返回至方法當前被調用的位置。方法退出的過程相當于彈出當前棧幀,退出可能有三種方式:

  • 返回值壓入上層調用棧幀。
  • 異常信息拋給能夠處理的棧幀。
  • PC計數器指向方法調用后的下一條指令。
  • 本地方法棧
    本地方法棧(Native Method Stack)與虛擬機棧所發揮的作用是非常相似的,它們之間的區別不過是虛擬機棧為虛擬機執行 Java 方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。Sun HotSpot 虛擬機直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區域也會拋出 StackOverflowError 和 OutOfMemoryError 異常。

    線程開始調用本地方法時,會進入 個不再受 JVM 約束的世界。本地方法可以通過 JNI(Java Native Interface)來訪問虛擬機運行時的數據區,甚至可以調用寄存器,具有和 JVM 相同的能力和權限。 當大量本地方法出現時,勢必會削弱 JVM 對系統的控制力,因為它的出錯信息都比較黑盒。對內存不足的情況,本地方法棧還是會拋出 nativeheapOutOfMemory。

    JNI 類本地方法最著名的應該是 System.currentTimeMillis() ,JNI使 Java 深度使用操作系統的特性功能,復用非 Java 代碼。 但是在項目過程中, 如果大量使用其他語言來實現 JNI , 就會喪失跨平臺特性。

    Java堆
    對于大多數應用來說,Java 堆(Java Heap)是 Java 虛擬機所管理的內存中最大的一塊。Java 堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。

    堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”(Garbage Collected Heap)。從內存回收的角度來看,由于現在收集器基本都采用分代收集算法,所以 Java 堆中還可以細分為:新生代和老年代;再細致一點的有 Eden 空間、From Survivor 空間、To Survivor 空間等。從內存分配的角度來看,線程共享的 Java 堆中可能劃分出多個線程私有的分配緩沖區(Thread Local Allocation Buffer,TLAB)。

    Java 堆可以處于物理上不連續的內存空間中,只要邏輯上是連續的即可,當前主流的虛擬機都是按照可擴展來實現的(通過 -Xmx 和 -Xms 控制)。如果在堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出 OutOfMemoryError 異常。

    方法區
    方法區(Method Area)與 Java 堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然
    Java 虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

    Java 虛擬機規范對方法區的限制非常寬松,除了和 Java 堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。垃圾收集行為在這個區域是比較少出現的,其內存回收目標主要是針對常量池的回收和對類型的卸載。當方法區無法滿足內存分配需求時,將拋出 OutOfMemoryError 異常。

    JDK8 之前,Hotspot 中方法區的實現是永久代(Perm),JDK8 開始使用元空間(Metaspace),以前永久代所有內容的字符串常量移至堆內存,其他內容移至元空間,元空間直接在本地內存分配。

    為什么要使用元空間取代永久代的實現?

  • 字符串存在永久代中,容易出現性能問題和內存溢出。
  • 類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
  • 永久代會為 GC 帶來不必要的復雜度,并且回收效率偏低
  • 將 HotSpot 與 JRockit 合二為一。
  • 運行時常量池
    運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放。

    一般來說,除了保存 Class 文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中。

    運行時常量池相對于 Class 文件常量池的另外一個重要特征是具備動態性,Java 語言并不要求常量一定只有編譯期才能產生,也就是并非預置入 Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的便是 String 類的 intern() 方法。

    既然運行時常量池是方法區的一部分,自然受到方法區內存的限制,當常量池無法再申請到內存時會拋出 OutOfMemoryError 異常。

    直接內存
    直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,也不是 Java 虛擬機規范中定義的內存區域。

    在 JDK 1.4 中新加入了 NIO,引入了一種基于通道(Channel)與緩沖區(Buffer)的 I/O 方式,它可以使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在 Java 堆中的 DirectByteBuffer 對象作為這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因為避免了在 Java 堆和 Native 堆中來回復制數據。

    顯然,本機直接內存的分配不會受到 Java 堆大小的限制,但是,既然是內存,肯定還是會受到本機總內存(包括 RAM 以及 SWAP 區或者分頁文件)大小以及處理器尋址空間的限制。服務器管理員在配置虛擬機參數時,會根據實際內存設置 -Xmx 等參數信息,但經常忽略直接內存,使得各個內存區域總和大于物理內存限制(包括物理的和操作系統級的限制),從而導致動態擴展時出現 OutOfMemoryError 異常。

    Java內存模型
    Java內存模型是共享內存的并發模型,線程之間主要通過讀-寫共享變量(堆內存中的實例域,靜態域和數組元素)來完成隱式通信。

    Java 內存模型(JMM)控制 Java 線程之間的通信,決定一個線程對共享變量的寫入何時對另一個線程可見。

    計算機高速緩存和緩存一致性
    計算機在高速的 CPU 和相對低速的存儲設備之間使用高速緩存,作為內存和處理器之間的緩沖。將運算需要使用到的數據復制到緩存中,讓運算能快速運行,當運算結束后再從緩存同步回內存之中。

    在多處理器的系統中(或者單處理器多核的系統),每個處理器內核都有自己的高速緩存,它們有共享同一主內存(Main Memory)。

    當多個處理器的運算任務都涉及同一塊主內存區域時,將可能導致各自的緩存數據不一致。

    為此,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議進行操作,來維護緩存的一致性。


    JVM主內存與工作內存
    Java 內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量(線程共享的變量)存儲到內存和從內存中取出變量這樣底層細節。

    Java內存模型中規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存中的變量。

    這里的工作內存是 JMM 的一個抽象概念,也叫本地內存,其存儲了該線程以讀 / 寫共享變量的副本。

    就像每個處理器內核擁有私有的高速緩存,JMM 中每個線程擁有私有的本地內存。

    不同線程之間無法直接訪問對方工作內存中的變量,線程間的通信一般有兩種方式進行,一是通過消息傳遞,二是共享內存。Java 線程間的通信采用的是共享內存方式,線程、主內存和工作內存的交互關系如下圖所示:


    這里所講的主內存、工作內存與 Java 內存區域中的 Java 堆、棧、方法區等并不是同一個層次的內存劃分,這兩者基本上是沒有關系的,如果兩者一定要勉強對應起來,那從變量、主內存、工作內存的定義來看,主內存主要對應于Java堆中的對象實例數據部分,而工作內存則對應于虛擬機棧中的部分區域。

    重排序和happens-before規則
    在執行程序時為了提高性能,編譯器和處理器常常會對指令做重排序。重排序分三種類型:

  • 編譯器優化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
  • 指令級并行的重排序。現代處理器采用了指令級并行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
  • 內存系統的重排序。由于處理器使用緩存和讀 / 寫緩沖區,這使得加載和存儲操作看上去可能是在亂序執行。
    從 java 源代碼到最終實際執行的指令序列,會分別經歷下面三種重排序:

    JMM 屬于語言級的內存模型,它確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內存可見性保證。
  • java 編譯器禁止處理器重排序是通過在生成指令序列的適當位置會插入內存屏障(重排序時不能把后面的指令重排序到內存屏障之前的位置)指令來實現的。

    happens-before
    從 JDK5 開始,java 內存模型提出了 happens-before 的概念,通過這個概念來闡述操作之間的內存可見性。

    如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必須存在 happens-before 關系。這里提到的兩個操作既可以是在一個線程之內,也可以是在不同線程之間。

    這里的“可見性”是指當一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的。

    如果 A happens-before B,那么 Java 內存模型將向程序員保證—— A 操作的結果將對 B 可見,且 A 的執行順序排在 B 之前。

    重要的 happens-before 規則如下:

  • 程序順序規則:一個線程中的每個操作,happens- before 于該線程中的任意后續操作。
  • 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 于隨后對這個監視器鎖的加鎖。
  • volatile 變量規則:對一個 volatile 域的寫,happens- before 于任意后續對這個 volatile 域的讀。
  • 傳遞性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。
  • 下圖是 happens-before 與 JMM 的關系

    volatile關鍵字
    volatile 可以說是 JVM 提供的最輕量級的同步機制,當一個變量定義為volatile之后,它將具備兩種特性:

  • 保證此變量對所有線程的可見性。而普通變量不能做到這一點,普通變量的值在線程間傳遞均需要通過主內存來完成。
    注意,volatile 雖然保證了可見性,但是 Java 里面的運算并非原子操作,導致 volatile 變量的運算在并發下一樣是不安全的。而 synchronized 關鍵字則是由“一個變量在同一個時刻只允許一條線程對其進行 lock 操作”這條規則獲得線程安全的。
  • 禁止指令重排序優化。普通的變量僅僅會保證在該方法的執行過程中所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致。
  • 總結

    以上是生活随笔為你收集整理的Java内存区域(运行时数据区域)和内存模型(JMM)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 一级久久久久久久 | 神马影院午夜伦理片 | 性欧美丰满熟妇xxxx性仙踪林 | 91精品国产综合久久久密臀九色 | 好吊视频一区二区三区四区 | 调教撅屁股啪调教打臀缝av | 中文字幕日韩在线观看 | 9l蝌蚪porny中文自拍 | 免费成人美女女电影 | 岛国片在线免费观看 | 强行无套内谢大学生初次 | 久久免费精品国产 | 午夜在线观看视频网站 | 亚洲丝袜在线观看 | 国产女人视频 | 久热欧美 | 钰慧的mv视频在线观看 | 天天视频国产 | 国产精品黑人一区二区三区 | 成年人免费毛片 | 久久黄色一级视频 | 国产一区二区三区视频免费在线观看 | 日本欧美国产一区二区三区 | 给我免费观看片在线电影的 | 天天干视频在线观看 | 国产精品美乳在线观看 | 精品精品精品 | 精品亚洲成人 | 日韩三级在线播放 | 色妞网站 | 午夜网站在线 | 免费av网址在线观看 | 青草国产视频 | 日本欧美在线视频 | 成人尹人 | 综合色导航 | 日韩精品免费一区二区三区 | 久久官网 | 欧美一级精品 | 国产一级一片免费播放放a 丁香六月色 | 日韩天堂在线视频 | 久久在线免费观看 | 欧美激情视频在线观看 | 精品日韩制服无码久久久久久 | 爱豆国产剧免费观看大全剧集 | 看久久| 91美女片黄在线观看 | 伊人久久一区二区三区 | 女人被男人躁得好爽免费视频 | 久久久久久福利 | 一卡二卡三卡四卡在线 | 国产精品ⅴa有声小说 | 午夜老司机免费视频 | 日日躁夜夜躁狠狠躁 | 欧美一区二区三区成人久久片 | 亚洲精品成人片在线观看精品字幕 | 六月丁香综合网 | 国产视频福利 | 国产第三页| 亚洲一级片免费看 | 欧美精品在线免费观看 | 一级免费在线观看 | h片在线观看网站 | av福利网站| 亚洲a级精品 | 亚洲熟妇中文字幕五十中出 | 麻豆国产在线播放 | 毛片基地站 | 成人免费网站www网站高清 | 激情av小说| 日本三级韩国三级美三级91 | 国产91在线免费观看 | 在线黄色大片 | 国产一级自拍视频 | 三上悠亚痴汉电车 | 亚洲欧美在线播放 | 精品少妇人妻av免费久久洗澡 | 女王脚交玉足榨精调教 | 在线免费不卡视频 | 国产日产欧美一区二区三区 | 疯狂做爰的爽文多肉小说王爷 | 成人av网址大全 | 凹凸日日摸日日碰夜夜 | 久久久久久不卡 | 日韩欧美麻豆 | 麻豆av片 | 国产精品中文在线 | 久久青草视频 | 一本色道久久88综合日韩精品 | 伊人9 | 日本a一级片 | 免费国产高清 | 成人午夜视频免费观看 | 黄色污污视频网站 | 亚洲一区二区三区播放 | 香蕉视频国产在线观看 | 欧美影院一区二区 | 精品91视频 | 91麻豆视频网站 |