1. 程式人生 > 程式設計 >JVM Memory Management

JVM Memory Management

JVM是什麼?

  在Java中,理解JDK、JRE、JVM的區別至關重要,利用JDK開發Java程式,通過JDK中的javac將.java檔案編譯成.class檔案,在JRE上執行.class檔案,JVM解析位元組碼,對映到CPU指令集或OS的系統呼叫。JVM是整個Java實現跨平臺的最核心部分,實現Java程式 write once,run anywhere.
 Java語言和JVM相對獨立,還可以在JVM上執行的語言如Groovy、Scala等。
複製程式碼

image
1.基礎類庫:java.lang、java.io、java.sql等 2.基本元件:javac、jar、javadoc、jdb、java、javap、jconsole、jvisualvm等 延伸閱讀:
docs.oracle.com/javase/8/do…

JVM Memory Management

image

程式計數器

   當前執行緒所執行的位元組碼的行號指示器,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要這個計數器來完成。Java虛擬機器器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式來實現的,在任何一個時刻,處理器的一個核心都只會執行一條執行緒的指令。
   如果執行緒執行一個Java方法,這個計數器記錄的是正在執行的虛擬機器器位元組碼指令地址;如果正在執行Native方法,這個計數器值為空
複製程式碼

虛擬機器器棧

  每個執行緒擁有自己的棧,棧包含每個方法執行的棧幀,棧幀包含區域性變量表、運算元棧、動態連結、方法出口等資訊。棧是一個後進先出(LIFO)的資料結構,因此當前執行的方法在棧的頂部。每次方法呼叫時,一個新的棧幀建立並壓棧到棧頂。當方法正常返回或丟擲未捕獲的異常時,棧幀就會出棧。 
(1)區域性變量表:基本資料型別、物件引用型別(reference型別,它不等同於物件本身,可能是一個指向物件起始地址的引用指標,也可能是控制程式碼 )和 returnAddress型別(指向了一條位元組碼指令的地址 ),其中64位長度long、double型別的資料會佔用2個區域性變數空間,其餘資料型別會佔用一個。區域性變量表所需的記憶體空間在編譯期確定。
(2)運算元棧:後進先出LIFO,最大深度由編譯期確定,用於存放JVM從區域性變量表複製的常量或變數,也用於儲存呼叫方法需要的引數及接受方法返回的結果
(3)動態連結:每個棧幀都指向了執行時常量池中該棧幀所屬方法的引用,
(4)方法出口:有兩種方式退出該方法,遇到了方法返回的位元組碼或沒有捕獲的異常。無論何種退出方式,都需要返回到方法呼叫的位置,程式才能繼續執行。方法退出的過程相當於把當前棧幀出棧,恢復上層方法的區域性變量表和運算元棧,如果有返回值,則把它壓入呼叫者棧幀的運算元棧中。  
複製程式碼

本地方法棧

  本地方法棧則為虛擬機器器使用的Native方法服務
複製程式碼

  所有的物件例項以及陣列都要在堆上分配,堆是垃圾收集管理的主要區域,又被稱為GC堆(Garbage Collected Heap),從記憶體回收的角度來看,現在收集器基本都採用分代收集演演算法: 新生代(Eden S0 S1)、老年代、持久代。根據Java虛擬機器器規範的規定,Java堆可以處理物理上不連續的記憶體空間,只要邏輯上連續就可以,當前主流的虛擬機器器都是按照可擴充套件實現的。
複製程式碼

方法區

 用於儲存載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。對於習慣在HotSpot虛擬機器器上開發、部署程式的開發者說,很多人都願意把方法區稱為“永久代”,本質上兩者不等價,僅僅是因為Hotspot的垃圾收集分代收集擴充套件至方法區,目前來說,使用方法區來實現永久代,更容易出現記憶體溢位問題(-XX:MaxPermSize)的上限,最典型的場景就是在jsp頁面比較多的情況下。對這塊區域回收很難,主要集中在常量池的回收和對型別的解除安裝。
複製程式碼

直接記憶體

JDK1.4中加入了NIO,非阻塞的IO方式。DirectByteBuffer中的unsafe.allocteMemory(size)是一個native方法,這個方法分配的是直接記憶體(堆外記憶體),底層是通過C的malloc分配的。分配的記憶體是系統本地的記憶體,不屬於JRE
複製程式碼

image

1.執行時常量池 Class檔案除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯期生成的各種字面量和符號引用,這部門內容將在類載入後進入方法區的執行時常量池存放。避免頻繁的建立物件和銷燬物件而影響系統效能,實現了物件的共享,例如字串常量池,在編譯階段就把所有字串文字放到一個常量池中。

(1)字面量:文字字串、被宣告為final常量值、基本資料型別的值和其他

(2)符號引用:類和結構的完全限定名、欄位名稱和描述符、方法名稱和描述符

元空間?

image

取消永久代

 JDK1.8已經從HotSpot JVM中移除了永久代,方法區中的類資訊被移至本地記憶體(native memory),字串常量、靜態變數被移到堆中。取消永久代,原因大致如下:
複製程式碼

(1)由於 PermGen Space 由 -XX:MaxPermSize 引數來決定大小,JVM在啟動時會分配一塊連續的記憶體塊,但隨著動態類載入情況越來越多,經常會出現OOM Error:PermGen 。如果配置過小,容易出現OOM異常;配置過大,造成記憶體浪費 (2)這是JRockit JVM和HotSpot JVM融合努力的一部分,JRockit JVM沒有永久代一說

概述

  方法區中的類資訊被移至本隊記憶體,這塊空間稱為元空間(Metaspace)。元空間的背後一個思想,類和它的元資料的生命週期和它的類載入器的生命週期是一致的;每個載入器的儲存區叫做“a metaspace”,這些“metaspace”一起總體稱為“the metaspace”。僅僅當類載入器被回收,該類載入器對應的元空間才可以回收。

 元空間使用塊分配器(chunk allocator)來管理元空間的記憶體分配,塊(chunk)的大小依賴於類載入器的型別,其中有一個全域性空閒塊列表(a global free list of chunks)。當類載入器需要一個塊的時候,類載入器從全域性塊列表中取出一個塊,新增到它自己維護的塊列表中。當類載入器死亡的時候,它的塊將被釋放,歸還給全域性空閒塊列表。

 塊會進一步被劃分成blocks,每個block儲存一個元資料單元(a unit of metadata),元空間使用由mmap分配。
複製程式碼

image

引數

-XX:MetaspaceSize=size 初始元空間大小,控制元空間發生GC的初始閾值,這個值的增加或者減少取決於已使用元資料大小,預設大小取決於平臺,範圍從12MB到20MB。
-XX:MaxMetaspaceSize=size 控制元空間最大值,並不會在JVM啟動時候分配這麼大記憶體。預設無大小限制,受限於系統本地記憶體大小。為防止無限制而導致metaspace記憶體洩漏而被OS殺掉,建議設定預設值
-XX:MinMetaspaceFreeRatio=size 最小的Metaspace剩餘空間容量百分比,當執行Metaspace GC之後,會計算當前Metaspace的空閒時間比,如果空閒比小於這個數,那麼虛擬機器器將增長Metaspace的大小。
-XX:MaxMetaspaceFreeRatio=size 最大的Metaspace剩餘空間容量百分比,當執行Metaspace GC之後,會計算當前Metaspace的空閒時間比,如果空閒比大於這個數,那麼虛擬機器器會釋放Metaspace的部分空間。

延伸閱讀:java-latte.blogspot.hk/2014/03/met…