• <center id="q6uyy"><td id="q6uyy"></td></center>
    <dd id="q6uyy"></dd>
  • 推廣 熱搜: csgo  vue  angelababy  2023  gps  新車  htc  落地  app  p2p 

    深入理解Java虛擬機學習之內存區域與內存溢出異常

       2023-08-07 網絡整理佚名2300
    核心提示:程序計數器是唯一沒有內存溢出()的區域)并不是虛擬機運行時數據區的一部分,這部分內存也被頻繁的使用,也可能導致異常出現其一通過代碼驗證各個運行時區域儲存的內容,其二根據異常提示信息迅速得知時哪個區域的內存溢出,怎樣的代碼可能會導致這些區域內存溢出,如何處理異常線程請求的棧深度大于虛擬機所允許的最大深度,將拋出異常虛擬機的棧內存允許動態擴展,當擴展容量無法申請到足夠的內存時,將拋出異常

    1 概述

    java中的內存管理是由虛擬機自動管理的。 雖然不需要手動清理和回收垃圾,但是當發生內存泄漏和溢出時,了解虛擬機如何使用內存對于Java程序員排查和糾正問題非常有幫助。 有幫助的

    2.運行時數據區

    Java程序執行過程中,Java虛擬機將其管理的內存劃分為幾個不同的數據區域,如下圖所示:

    2.1. 程序計數器

    程序計數器是一個很小的內存空間,可以將其視為當前線程正在執行的字節碼行號的指示器。 它是程序控制流程的指標,分支、循環、跳轉、異常處理、線程恢復等基本功能都需要依賴它來完成。

    為了線程切換后能夠回到正確的執行位置,每個線程都需要有一個獨立的程序計數器。 線程之間的計數器互不影響,獨立存儲。 因此,程序計數器是線程私有的。

    程序計數器是唯一沒有溢出()的區域

    2.2. Java虛擬機棧

    Java虛擬機棧為虛擬機執行Java方法提供服務。 它的生命周期和線程一樣,也和程序計數器一樣是線程私有的。 每個方法執行時,Java虛擬機都會同步創建一個棧幀(Stack frame),用于存儲局部變量表、操作數棧、動態鏈接、方法出口等。每個方法被調用直到執行完成的過程都對應虛擬機中棧幀從入棧到出棧的過程。

    如果線程請求的堆棧深度大于虛擬機允許的深度,則會拋出異常

    如果Java虛擬機的棧容量可以動態擴展,那么當棧擴展時,如果沒有足夠的內存可以申請,就會拋出異常。

    2.3. 原生方法棧

    本地方法棧為虛擬機棧使用的本地方法提供服務。 和虛擬機棧一樣,當棧深度溢出或者擴展失敗時,也會分別拋出異常和異常。

    2.4. Java堆

    Java堆是虛擬機管理的內存中最大的區域。 它是所有線程共享的內存區域。 它是在虛擬機啟動時創建的,其目的是存儲對象實例。

    Java堆是由垃圾收集器管理的內存區域,因此也稱為GC堆。

    Java堆可以位于物理上不連續的內存空間中,但邏輯上應該被認為是連續的。 Java 堆可以實現為固定大小,也可以擴展。 如果Java堆中沒有內存來完成實例分配,并且堆無法再擴展,Java虛擬機就會拋出異常。

    2.5. 方法區

    與Java堆一樣,方法區是每個線程共享的內存區域,用于存儲已加載的類型信息、常量、靜態變量以及即時編譯器編譯出來的代碼緩存等數據。虛擬機。

    方法區不需要連續的內存,可以選擇固定大小或可擴展。 您也可以選擇不實施垃圾收集。 這方面的垃圾收集行為比較少見,主要是常量池的回收和類型的卸載。

    如果方法區不能滿足新的內存分配要求,則會拋出異常

    2.6。 運行時常量池

    運行時常量池(Pool)是方法區的一部分。 Class文件中有類的版本、字段、方法、接口和常量池表。 常量池表用于存儲編譯期間生成的各種文字和合規性引用。 類加載后,部分內容會存儲在方法區的運行時常量池中。

    常量池在運行時的另一個重要特點是它是動態的。 Java語言并不要求常量只能在編譯時生成,即Class文件中未預設的常量池內容可以在運行時在方法區進入常量池。 也可以將新的常量放入池中,例如類的()方法。

    如果常量池無法再申請內存,就會拋出異常。

    2.7. 直接記憶

    直接內存()在虛擬機運行時不屬于數據區,這部分內存也被頻繁使用,也可能會引起異常

    3.虛擬機對象探索

    是最常用的虛擬機

    3.1. 對象創建

    類加載檢查-->為新生對象分配內存-->初始化-->設置元數據、哈希碼、GC分代年齡等信息-->()方法

    類加載檢查。 當Java虛擬機遇到字節碼new指令時,首先會檢查該指令的參數是否能在常量池中定位到某個類的符合引用,并檢查該符合引用所代表的類是否已經被加載、解析和釋放。已初始化,如果沒有,那么必須先執行相應的類加載過程。

    為新生對象分配內存。 對象所需內存的大小在類加載完成后就可以完全確定,而為對象分配空間的任務實際上相當于從Java堆中劃分出一定大小的內存塊。

    指針碰撞(Bump):假設Java堆內存絕對規則,所有已用內存放在一側,空閑內存放在另一側,中間放置一個指針作為分界點的指示符,那么分配內存只是將指針向空閑空間方向移動等于對象大小的距離。

    空閑列表(Free List):如果Java堆中的內存不規則,并且使用的內存和空間內存相互交錯,那么沒有辦法簡單地碰撞指針,虛擬機必須維護一個列表,記錄哪些內存塊可用,分配時從鏈表中找到足夠大的空間分配給對象實例,并更新鏈表上的記錄

    選擇哪種分配方式取決于Java堆是否規則,而是否規則又取決于所使用的垃圾收集器是否具有壓縮和組織的能力()。

    當使用帶有壓縮過程的收集器時,系統采用的分配算法是指針碰撞,簡單高效。

    當使用CMS等基于Sweep算法的收集器時,理論上只能使用更復雜的空閑列表來分配內存。

    內存創建是虛擬機中非常頻繁的行為。 即使只修改指針指向的位置,在并發情況下也不是線程安全的。 可能會發生這樣的情況:內存正在分配給對象 A,而指針還沒有來得及修改它。 對象B同時使用原來的指針來分配內存。

    方案一:同步分配內存空間的動作。 事實上,虛擬機使用帶有失敗重試的CAS來保證更新操作的原子性。

    方案二:根據線程將內存分配劃分到不同的空間。 即每個線程在Java堆中預先分配一小塊內存,稱為本地線程分配緩沖區(Local,TLAB)。 無論哪個線程想要分配內存,都會在該線程的本地緩沖區中分配,并且只有本地緩沖區被用完。 ,僅當分配新緩沖區時才需要同步鎖。

    虛擬機是否使用TLAB可以通過-XX:+/-參數設置。

    將分配的內存空間(不包括對象頭)初始化為零值。 如果使用TLAB,這項工作也可以在分配TLAB時提前完成。 它保證了對象的實例字段可以在Java代碼中直接使用而無需分配初始值,從而程序可以訪問與這些字段的數據類型對應的零值。

    對對象進行必要的設置,比如對象是哪個類的實例,如何查找類的元數據信息,對象的哈希碼,對象的GC分代年齡等信息。

    至此,對于虛擬機來說,一個新的對象已經生成了。 從Java程序的角度來看,對象的創建開始——構造函數,new指令后會執行()方法

    3.2. 對象內存布局

    對象在堆內存中的存儲布局分為:對象頭()、實例數據(Data)和對齊填充()

    對象頭包含兩類信息:

    第一種用于存儲對象本身的運行時數據,如哈希碼()、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等,以32位形式存儲虛擬機中間是32位,64位虛擬機中間是64位。

    當對象沒有被同步鎖鎖定時,32位存儲空間中的25個用于存儲對象的哈希碼,4個用于存儲對象的分代年齡,2個用于存儲鎖標志,并且 1 固定為 0。

    輕量級鎖、重量級鎖、GC標記、可偏向時間如下:

    存儲內容標志狀態

    對象哈希碼、對象分代年齡

    01

    解鎖

    指向鎖記錄的指針

    00

    輕量級鎖定

    指向重量級鎖的指針

    10

    膨脹(重量級鎖)

    空,無需記錄信息

    11

    GC標志

    偏置線程 ID、偏置時間戳、對象分代年齡

    01

    可能有偏見

    第二種類型是類型指針,即對象指向其類型元數據的指針,Java虛擬機通過這個指針來確定該對象是哪個類的實例

    實例數據部分是對象實際存儲的有效信息,即程序代碼中定義的各類字段的內容,存儲順序會受到虛擬機分配策略參數(-XX:e參數)的影響)以及Java源代碼中字段的定義順序。

    默認的分配順序是longs/, ints, /chars, bytes/, oops( , OOPs),相同寬度的字段總是分配在一起存儲。 除此之外,父類中定義的變量會出現在子類之前,如果+XX:參數值為true,則允許子類中較窄的變量插入到父類變量的間隙中。

    對齊填充不一定存在,也沒有特殊含義,它僅充當占位符。 任何對象的大小必須是 8 字節的整數倍。 如果對象實例的數據部分沒有對齊,則需要通過對齊填充來填充。

    3.3. 對象訪問位置

    主流的訪問方式是使用句柄和直接指針

    使用句柄——可能會在Java堆中分配一塊內存作為句柄池,里面存儲了對象的句柄地址,句柄中包含了對象實例數據和類型數據的具體地址信息

    使用直接指針——Java堆中對象的內存布局必須考慮如何放置要訪問的數據類型的信息。 對象地址直接存儲在Java堆中。 如果只訪問對象本身,則不需要間接訪問開銷。

    兩種比較方法:

    訪問優勢

    使用手柄

    存儲的地址是一個穩定的句柄。 對象移動時,只會改變句柄中的實例數據指針,不需要修改

    使用直接指針

    速度更快,節省了指針定位的時間開銷

    注:虛擬機主要使用直接指針進行對象訪問

    3.4. 實戰:異常

    一是通過代碼驗證各個運行時區域存儲的內容,二是根據異常提示信息快速知道是哪個區域內存溢出,什么樣的代碼可能會導致這些區域內存溢出,以及如何處理。來處理異常

    3.4.1. Java堆溢出

    不斷創建對象,并保證GC Roots和對象之間有可達路徑,以避免垃圾收集機制清除這些對象,那么隨著對象數量的增加,當總容量達到最大堆容量限制時,就會出現內存溢出異常發生

    解決方案

    首先,使用內存映像分析工具來分析Dump中的堆轉儲快照。 第一步是確認內存中導致OOM的對象是否是必要的,并區分是否存在內存泄漏(Leak)或內存溢出()。

    對于內存泄漏,通過工具進一步檢查從泄漏對象到GC Roots的引用鏈,找出泄漏對象關聯的是什么樣的引用路徑以及哪些GC Roots,這樣垃圾收集器就無法回收它們。 根據泄漏對象的類型信息和GC Roots引用鏈的信息可以定位到該對象被創建的位置,進而找出產生內存泄漏的代碼位置。

    內存溢出,檢查Java虛擬機的堆參數(-Xmx和-Xms)設置,與機器的內存進行比較,是否還有向上調整的空間,然后檢查代碼中是否有某些對象存在內存溢出問題。生命周期長、保持狀態時間過長、存儲結構設計不合理等。

    3.4.2. 虛擬機堆棧和本機方法堆棧溢出

    虛擬機棧和本地方法棧有兩個例外:

    線程請求的棧深度大于虛擬機允許的最大深度,會拋出異常。 虛擬機的堆棧內存允許動態擴展。 當擴展容量無法申請到足夠的內存時,會拋出異常

    不同版本的Java虛擬機和不同的操作系統對最小堆棧容量都有限制,這主要取決于操作系統的內存分頁大小。

    線程過多引起的內存溢出,可以通過減少線程數或更換64位虛擬機或減少最大堆、棧容量來換取更多線程。 從JDK7開始,會提示out of 或 /

    3.4.3. 方法區和運行時常量池溢出

    JDK6運行時常量池溢出時,異常后面的提示信息為“空格”,運行時常量池屬于方法區

    JDK8使用元空間而不是永久代。 默認情況下,不會發生此異常。 提供一些參數作為元空間防御措施

    -XX::設置元空間的最大值,默認為-1,即沒有限制,或者只受本地內存大小限制

    -XX::指定元空間的初始空間大小,以字節為單位。 當達到這個值時,就會觸發垃圾收集進行類型卸載,收集器會調整這個值:如果釋放大量空間,就會適當減小這個值; 如果釋放的空間很少,請適當增加該值,但不要超過-XX:

    -XX:o:控制垃圾回收后最小元空間剩余容量的百分比,可以減少因元空間不足而進行垃圾回收的頻率

    -XX:Max-:用于控制元空間最大剩余容量的百分比

    3.4.4. 本機直接內存溢出

    直接內存容量的大小可以通過-XX:參數指定,默認與Java堆的最大值(-Xmx指定)一致

    當內存溢出時,Heap Dump文件中不會看到明顯的異常。 發現內存溢出后生成的Dump文件較小,程序直接或間接使用(如間接使用NIO)

     
    反對 0舉報 0 收藏 0 打賞 0評論 0
     
    更多>同類資訊
    推薦圖文
    推薦資訊
    點擊排行
    網站首頁  |  關于我們  |  聯系方式  |  使用協議  |  版權隱私  |  網站地圖  |  排名推廣  |  廣告服務  |  積分換禮  |  網站留言  |  RSS訂閱  |  違規舉報
    Powered By DESTOON
     
    三级精品影视国产,欧美乱伦免费综合,亚洲a在线中文,人妻色综合网站
  • <center id="q6uyy"><td id="q6uyy"></td></center>
    <dd id="q6uyy"></dd>