java內存管理機制剖析(一)

最近利用工做之餘學習研究了一下java的內存管理機制,在這裏記錄總結一下。 ###1.1 java內存區域 當java程序運行時,java虛擬機會將內存劃分爲若干個不一樣的數據區域,這些內存區域建立和銷燬的時間各不相同,所承擔的功能也不相同,他們各司其職,各盡所責。這些區域的劃分以下圖 java

image.png

運行時數據區主要有五個區,分別是 堆 ,方法區,虛擬機棧,本地方法棧,程序計數器,下面我來一一詳細講解這五個數據區 ####堆 java堆是java虛擬機管理內存中最大的一塊,它是被全部線程共享的一塊內存區域,在虛擬機啓動時建立,此內存的惟一目的就是存放對象實例,幾乎全部的對象實例以及數組都在堆分配內存數組

java虛擬機規定,java堆能夠處於物理上不連續的內存空間中,只要邏輯上連續便可。在實現時,既能夠實現固定大小的,也能夠是擴展的,能夠經過配置-Xmx和-Xms來擴展大小。若是堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError ####方法區 方法區也是被全部線程共享的一塊內存區域,在Java虛擬機規範中,方法區是堆的邏輯組成部分,但他又被與堆區分開來,別名稱爲Non-Heap,它主要的存儲內容有下面幾點多線程

  • 類型的完整有效名
  • 類型直接父類的完整有效名
  • 類型的修飾符(public,abstract,final的某個子集)
  • 類型的常量池
  • 域(Field)信息
  • 方法(Method)信息
  • 除了常量外的全部靜態(static)變量

總結起來就是主要用於存儲已被虛擬機加載的類信息,常量,靜態變量,編譯器編譯後的代碼等數據併發

這裏我在介紹一下常量池,域信息和方法信息jvm

---常量池

常量池也稱爲運行時常量池(Runtime Constant Pool),用於存放編譯期生成的各類字面量和符號引用,它是這個類型用到的常量的一個有序集合,包括實際的常量(String, Integer, 和Floating point常量)和類型,域和方法的符號引用。 池中的數據項像數組項同樣,是經過索引訪問的。 由於常量池存儲了一個類類型所使用到的全部類型,域和方法的符號引用,因此它在java程序的動態連接中起了核心的做用學習

域(Field)信息

域的相關信息包括:**域名; 域類型; 域修飾符(public, private, protected,static,final volatile,transient的某個子集) **線程

方法(Method)信息

方法的相關信息包括:方法名, 方法的返回類型(或 void), 方法參數的數量和類型(有序的),方法的修飾符(public, private, protected, static, final, synchronized, native, abstract的一個子集),除了abstract和native方法外,其餘方法還有保存方法的字節碼(bytecodes)操做數棧和方法棧幀的局部變量區的大小指針

java虛擬機規範對方法區的限制比較寬鬆,除了和java堆同樣不須要連續的內存和能夠選擇固定大小或者可擴展外,還能夠選擇不是實現垃圾收集。垃圾收集行爲在方法區也比較少出現,當方法區沒法知足內存分配時,會拋出OutOfMemoryErrorcode

虛擬機棧

虛擬機棧是線程私有的,它的生命週期與線程相同,當咱們start一個線程時,jvm會爲當前線程開闢一塊虛擬機棧,噹噹前線程死亡時,線程的虛擬機棧也會銷燬。cdn

代碼中每一個方法在執行的同時,都會建立一個棧幀(Stack Frame)用於存儲局部變量表,操做數棧,動態連接,方法出口等信息。每個方法調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。

局部變量表存放了編譯期可知的各類基本數據類型(boolean,byte,char,short,int,float,long,double),對象引用和returnAddreass類型

JVM對這個區域規定了兩種異常狀況:若是線程請求的棧深度大於虛擬機所容許的深度,將拋出SstackOverFlowError異常;若是虛擬機棧能夠動態擴展,若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryErro異常

本地方法棧

本地方法棧和虛擬機棧的做用是同樣的,只不過本地方法棧是虛擬機執行java方法時開闢的棧,而本地方法棧是虛擬機用到Native方法時,開闢的棧。

程序計數器

程序計數器是一塊較小的內存,它能夠看做是當前線程的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工做時就是經過改變這個計數器的值來選取嚇一跳須要執行的字節碼指令,分支,循環,跳轉,異常處理,線程恢復等基礎功能。

因爲java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器都只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各線程指尖計數器互不影響,獨立存儲。

1.2 對象建立

瞭解了內存的數據區域,咱們能夠進一步瞭解對象是如何建立的了。這裏先經過一張流程圖一窺java的對象建立過程

image.png
能夠看到,當虛擬機遇到一條new指令時,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已經加載。若是沒有,則執行加載。

加載完成後,便會在堆中爲對象分配內存,JVM有兩種分配方式①指針碰撞,②空閒列表,下面我詳細講講這兩種分配方式。

---指針碰撞

當java堆中內存是整齊的,全部用過的內存都放一邊,空閒的內存放在另外一邊,中間放着一個指針座位分界點的指示器,那所分配內存就僅僅是把那個指針向空閒空間那邊摞動一段與對象大小相等的距離,這種分配就叫指針碰撞

---空閒列表

當Java堆的內存並非完整的,已分配的內存和空閒內存相互交錯,JVM經過維護一個列表,記錄可用的內存塊信息,當分配操做發生時,從列表中找到一個足夠大的內存塊分配給對象實例,並更新列表上的記錄。這種分配方式稱爲空閒列表

當JVM所採用的垃圾收集器帶有壓縮整理功能時,java堆是規整的,這個時候會採用指針碰撞分配內存,不然會採用空閒列表分配內存。對象建立是一個很是頻繁的行爲,進行堆內存分配時還須要考慮多線程併發問題,可能出現正在給對象A分配內存,指針或記錄還未更新,對象B又同時分配到原來的內存,解決這個問題有兩種方案:

  • 一、採用CAS保證數據更新操做的原子性;
  • 二、把內存分配的行爲按照線程進行劃分,在不一樣的空間中進行,每一個線程在Java堆中預先分配一個內存塊,稱爲本地線程分配緩衝(Thread Local Allocation Buffer, TLAB);
相關文章
相關標籤/搜索