您好,登錄后才能下訂單哦!
最近利用工作之余學習研究了一下java的內存管理機制,在這里記錄總結一下。
當java程序運行時,java虛擬機會將內存劃分為若干個不同的數據區域,這些內存區域創建和銷毀的時間各不相同,所承擔的功能也不相同,他們各司其職,各盡所責。這些區域的劃分如下圖
運行時數據區主要有五個區,分別是 堆 ,方法區,虛擬機棧,本地方法棧,程序計數器 ,下面我來一一詳細講解這五個數據區
java堆是java虛擬機管理內存中最大的一塊,它是被所有線程共享的一塊內存區域,在虛擬機啟動時創建, 此內存的唯一目的就是存放對象實例,幾乎所有的對象實例以及數組都在堆分配內存 。
java虛擬機規定,java堆可以處于物理上不連續的內存空間中,只要邏輯上連續即可。在實現時,既可以實現固定大小的,也可以是擴展的,可以 通過配置-Xmx和-Xms來擴展大小 。如果堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出 OutOfMemoryError
方法區也是被所有線程共享的一塊內存區域,在Java虛擬機規范中,方法區是堆的邏輯組成部分,但他又被與堆區分開來,別名稱為Non-Heap,它主要的存儲內容有下面幾點
總結起來就是主要用于存儲已被虛擬機加載的類信息,常量,靜態變量,編譯器編譯后的代碼等數據
這里我在介紹一下常量池,域信息和方法信息
常量池也稱為運行時常量池(Runtime Constant Pool),用于存放編譯期生成的各種字面量和符號引用,它是這個類型用到的常量的一個有序集合,包括
實際的常量(String, Integer, 和Floating point常量)和類型,域和方法的符號引用
。
池中的數據項像數組項一樣,是通過索引訪問的。 因為常量池存儲了一個類類型所使用到的所有類型,域和方法的符號引用,所以它在java程序的動態鏈接中起了核心的作用
域的相關信息包括: 域名; 域類型; 域修飾符(public, private, protected,static,final volatile,transient的某個子集)
方法的相關信息包括: 方法名, 方法的返回類型(或 void), 方法參數的數量和類型(有序的),方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個子集) ,除了abstract和native方法外,其他方法還有保存方法的 字節碼(bytecodes)操作數棧和方法棧幀的局部變量區的大小 。
java虛擬機規范對方法區的限制比較寬松,除了和java堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不是實現垃圾收集。垃圾收集行為在方法區也比較少出現,當方法區無法滿足內存分配時,會拋出 OutOfMemoryError
虛擬機棧是線程私有的,它的生命周期與線程相同,當我們start一個線程時,jvm會為當前線程開辟一塊虛擬機棧,當當前線程死亡時,線程的虛擬機棧也會銷毀。
代碼中每個方法在執行的同時,都會創建一個棧幀(Stack Frame)用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息。每一個方法調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。
局部變量表存放了編譯期可知的各種基本數據類型(boolean,byte,char,short,int,float,long,double),對象引用和returnAddreass類型
JVM對這個區域規定了兩種異常情況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出SstackOverFlowError異常;如果虛擬機棧可以動態擴展,如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryErro異常
本地方法棧和虛擬機棧的作用是一樣的,只不過本地方法棧是虛擬機執行java方法時開辟的棧,而本地方法棧是虛擬機用到Native方法時,開辟的棧。
程序計數器是一塊較小的內存,它可以看作是當前線程的字節碼的行號指示器。在虛擬機的概念模型里,字節碼解釋器工作時就是通過改變這個計數器的值來選取嚇一跳需要執行的字節碼指令,分支,循環,跳轉,異常處理,線程恢復等基礎功能。
由于java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各線程指尖計數器互不影響,獨立存儲。
了解了內存的數據區域,我們可以進一步了解對象是如何創建的了。這里先通過一張流程圖一窺java的對象創建過程
可以看到,當虛擬機遇到一條new指令時,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已經加載。如果沒有,則執行加載。
加載完成后,便會在堆中為對象分配內存,JVM有兩種分配方式 ①指針碰撞,②空閑列表 ,下面我詳細講講這兩種分配方式。
當java堆中內存是整齊的,所有用過的內存都放一邊,空閑的內存放在另一邊,中間放著一個指針座位分界點的指示器,那所分配內存就僅僅是把那個指針向空閑空間那邊摞動一段與對象大小相等的距離,這種分配就叫指針碰撞
當Java堆的內存并不是完整的,已分配的內存和空閑內存相互交錯,JVM通過維護一個列表,記錄可用的內存塊信息,當分配操作發生時,從列表中找到一個足夠大的內存塊分配給對象實例,并更新列表上的記錄。這種分配方式稱為空閑列表
當JVM所采用的垃圾收集器帶有壓縮整理功能時,java堆是規整的,這個時候會采用指針碰撞分配內存,否則會采用空閑列表分配內存。對象創建是一個非常頻繁的行為,進行堆內存分配時還需要考慮多線程并發問題,可能出現正在給對象A分配內存,指針或記錄還未更新,對象B又同時分配到原來的內存,解決這個問題有兩種方案:
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。