1. 程式人生 > >【Java】【JVM】Java中JVM記憶體管理 2018-10-5

【Java】【JVM】Java中JVM記憶體管理 2018-10-5

Java中JVM記憶體管理

JVM記憶體劃分:

① 方法區 (執行緒共享) 常量 靜態變數 JIT(即時編譯器)編譯後代碼也在方法區存放

② 堆記憶體(執行緒共享) 垃圾回收的主要場地

③ 程式計數器 當前執行緒執行的位元組碼的位置指示器

④ Java虛擬機器棧(棧記憶體) :儲存區域性變數,基本資料型別以及堆記憶體中物件的引用變數

⑤ 本地方法棧 (C棧):為JVM提供使用native方法的服務

在這裡插入圖片描述

JDK 1.8同JDK 1.7 ,最大的區別是:元資料取代了永久代.元空間的本質和永久代類似,都是對JVM規範中的方法區的實現.其元空間和永久代之間的最大區別在於:元資料空間不在虛擬機器中,而是在本地記憶體中

01)程式計數器(PC暫存器)

程式計數器的定義: 程式計數器是一塊較小的記憶體空間,是當前執行緒正在執行的哪一條位元組碼指令的地址,若當前執行緒正在執行的是一個本地方法,那麼此時程式計數器為Undefined

程式計數器的作用:

位元組碼直譯器通過改變程式計數器來依次獲取指令,從而實現程式碼的流程的控制

在在多執行緒情況下,程式計數器記錄的是當前執行緒執行的執行的位置,從而當執行緒切換回來時,就知道上次執行緒執行到哪了

程式計數器的特點

是一塊較小的記憶體空間

執行緒私有,每個執行緒都有自己的程式計數器

生命週期:隨著執行緒的建立而建立,隨著執行緒的銷燬而銷燬

是一個唯一不會出現的OutOfMemoryError的記憶體區域

02)Java虛擬機器棧

** 定義:

描述Java方法執行過程的記憶體模型

Java虛擬機器棧會為每一個即將執行的Java方法建立一塊叫做"棧幀"的區域,用於存放該方法執行過程中的一些資訊,如 區域性變量表 /運算元棧 /動態連結 /方法出口資訊 …

壓棧出棧過程:

當方法執行過程中需要建立區域性變數時,就將區域性變數的值存入棧幀的區域性變量表中

Java虛擬機器棧的棧頂是當前正在執行的活動棧,也就是當前正在執行的方法,PC暫存器也會指向這個地址,只有這個活動的棧幀的本地變數可以被運算元棧操作,當前這個棧幀中呼叫另一個方法,與之對應的額棧幀又會被建立,新建立的棧幀壓入棧頂,變成當前的活動棧幀,方法結束後,當前棧幀的返回值變成新的活動棧幀的中的運算元棧的一個運算元,如果沒有返回值,那麼新的活動棧幀中運算元棧的運算元沒有變化

由於Java虛擬機器棧是執行緒對應的,資料不是共享的,因此不用關心資料一致性問題,也不會存在同步鎖的問題

特點

區域性變量表隨著棧幀的建立而建立,他的大小在編譯時確定,建立時只需分配事先規定的大小即可,在方法執行的過程中,區域性變化表的大小不會發生變化

Java虛擬機器棧會出現兩種異常:StackOverFlowError和OutOfMemoryError

StackOverFlowError若Java虛擬機器棧的大小不允許動態擴充套件,那麼當前執行緒請求的棧的深度超過當前的Java虛擬機器棧的最大深度是,就會丟擲此異常

OutOFMemoryError,若允許動態擴充套件,那麼當前執行緒的請求的棧記憶體用完了,無法再動態擴充套件時,丟擲此異常

Java虛擬機器棧也是執行緒私有,隨著執行緒建立而建立,隨著執行緒的結束而銷燬

在這裡插入圖片描述

03)本地方法棧(C棧)

定義: 是為了JVM執行native方法準備的空間,由於很多native方法都是用C語言實現的,所以通常又叫C棧,它與Java虛擬機器棧實現的功能類似,只不過本地方法棧描述本地方法執行過程的記憶體模型

棧幀變化過程:

本地方法被執行時,在本地方法棧也會建立一塊棧幀,用於存放該方法的區域性變量表 /運算元棧 /動態連結 /方法出口等資訊; 方法結束後,相應的棧幀也會出棧,並釋放記憶體空間.也會丟擲StackOverFlowError和OutOfMemoryError異常

04) 堆

定義: 堆是用來物件的記憶體空間,幾乎所有的物件都儲存在堆中

特點:

執行緒共享,整個Java虛擬機器只有一個堆,所有執行緒都訪問同一個堆.

在虛擬機器啟動時建立

是垃圾回收的主要場地

進一步可分為:新生代(Eden區 From Survior To Surviror) 老年代

不同的區域存放的不同生命週期的物件,這樣可以根據不同區域使用不同的垃圾回收演算法,更具有針對性. 堆的大小也可以固定也可以擴充套件,對於主流的虛擬機器,堆大小可擴充套件的,因此當執行緒請求分配的記憶體,但堆已滿,且記憶體已無法再擴充套件,就丟擲OutOfMemoryError異常

05)方法區

定義: Java虛擬機器規範中定義方法區是堆的一個邏輯部分,方法區存放以下資訊 已被虛擬機器載入的類資訊 /常量 /靜態變數 /即時編譯後代碼

特點:

執行緒共享.方法區是堆的一個邏輯部分,因此和堆一樣,都是執行緒共享,整個虛擬機器中只有一個方法區

永久代 方法區中的資訊一般需要長期存在,而且它又是堆的邏輯分割槽,因此用堆的劃分方法,把方法區稱為"永久代"

記憶體回收的效率低.方法區中的資訊一般需要長期存在,回收一遍只有少量資訊無效.主要回收的目標是: 對常量池的回收;對型別的解除安裝

Java虛擬機器規範l對方法區的要求比較寬鬆,和堆一樣,允許固定大小.也允許動態擴充套件,還允許不實現垃圾回收

執行時常量池:

方法區中存放:類資訊 常量 靜態變數 即時編譯器變編譯後代碼.常量就存放在執行時常量池中.當類被Java虛擬機器載入後,.class檔案中的常量就存在方法區的執行常量池,而且在執行期間,可以向常量池中新增新的常量,如String類的intern()方法就能在執行期間向常量池中新增字串常量

06) 直接記憶體(堆外記憶體)

直接記憶體是除Java虛擬機器之外的記憶體,但有可能被Java使用

操作直接記憶體:

在NIO中引入了一種基於通道和快取的IO方式,他可以呼叫本地方法的直接分配Java虛擬機器之外的記憶體,然後通過一個儲存在堆中的DirectByteBuffer物件直接操作該記憶體,而無需將外部記憶體中資料複製到堆中再進行操作,從而提高資料操作的效率,直接記憶體的大小不受Java虛擬機器,也會丟擲OutOfMemoryError異常

直接記憶體和堆記憶體比較

直接記憶體申請空間耗費更高的效能

直接記憶體讀取IO的效能優於普通的堆記憶體

直接記憶體的作用鏈:本地IO–>直接記憶體–>本地IO

堆記憶體的作用鏈:本地IO–>直接記憶體–>非直接記憶體–>直接記憶體—>本地IO

伺服器管理員在配置虛擬機器引數時,會根據實際記憶體設定 -Xmx等引數資訊,但經常忽略直接記憶體,使得各個記憶體區域總和大於實體記憶體,從而導致動態擴充套件時出現OutOFMemoryError

類似 -Xms -Xmn這些引數的含義

堆記憶體分配

① : JVM初始分配的記憶體由-Xms指定,預設是實體記憶體的1/64

②: JVM最大分配的記憶體由-Xmx指定,預設是實體記憶體的1/4

③: 預設空餘堆記憶體小於40%時,JVM就會增加堆直到-Xmx的最大限制;空餘堆記憶體大於70%時,JVM會減少堆直到-Xms的最小限制

④: 因此伺服器一般設定-Xms -Xmx相等以避免在每次GC後調整堆大小. 物件的堆記憶體由成為垃圾回收器的自動記憶體管理系統回收

非堆記憶體分配:

①:JVM使用-XX:PermSize 設定非堆記憶體的初始值,預設實體記憶體的1/64;

② :由XX:MaxPermSize設定設定最大非堆記憶體的大小

③: -Xmn2G :設定年輕代的大小為2G

④ :-XX:SurvivorRatio ,設定年輕代中Eden區與Survivor區的比值

垃圾回收的演算法有哪些?

① 引用計數法:原理是在此物件有個引用,即增加一個計數,刪除一個引用則減少一個計數.垃圾回收時,只收集計數為0的物件.此演算法的最致命的無法處理迴圈引用的問題

②: 標記-清除 :此演算法分兩個階段,第一階段從引用的根節點開始標記所有被引用的物件,第二階段遍歷整個堆,把未標記的物件清除,此演算法需要暫停應用,同時產生記憶體碎片

③: 複製演算法 此演算法把記憶體劃分為兩個相等的區域,每次只使用一個區域,垃圾回收時,遍歷當前使用的區域,把正在使用的物件複製到另一個區域中每次演算法每次只處理正在使用的物件,因此複製的成本比較小,同時複製過去以後還能進行相應的記憶體整理,不會出現"碎片問題",此演算法的缺點也很明顯,需要兩倍的記憶體空間

④: 標記-整理:此演算法結合了"標記-清除"和:複製演算法的兩個的優點,也是分兩個階段,第一個階段從根節點開始標記所有被引用物件,第二階段遍歷整個堆,清除未標記的物件並且把存活的物件"壓縮"到堆的其中一塊,按順序排放,此演算法避免"標記-清除"的碎片問題,同時也避免"複製"的空間問題

root搜尋演算法中,哪些可以作為root?

  • 被啟動類(bootstrap載入器)載入的類和建立的物件
  • JavaStack中引用的物件(棧記憶體中引用的物件)
  • 方法區中靜態引用