Java虛擬機器的記憶體組成以及堆記憶體介紹
什麼是Java虛擬機器這裡就不介紹了,不明白的可以另外一篇博文:JDK,JRE,JVM區別與聯絡
一、java記憶體組成介紹:堆(Heap)
和非堆(Non-heap)
記憶體
按照官方的說法:“Java 虛擬機器具有一個堆,堆是執行時資料區域,所有類例項和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。”“在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)”。可以看出JVM主要管理兩種型別的記憶體:堆和非堆。簡單來說堆就是Java程式碼可及的記憶體,是留給開發人員使用的;非堆就是JVM留給 自己用的,所以方法區、JVM內部處理或優化所需的記憶體(如JIT編譯後的程式碼快取)、每個類結構(如執行時常數池、欄位和方法資料)以及方法和構造方法 的程式碼都在非堆記憶體中。
二、JVM記憶體區域模型
1.方法區 也稱”永久代” 、“非堆”, 它用於儲存虛擬機器載入的類資訊、常量、靜態變數、是各個執行緒共享的記憶體區域。預設最小值為16MB,最大值為64MB,可以通過-XX:PermSize
和 -XX:MaxPermSize
引數限制方法區的大小。
執行時常量池:是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池,用於存放編譯器生成的各種符號引用,這部分內容將在類載入後放到方法區的執行時常量池中。
2.虛擬機器棧
描述的是java 方法執行的記憶體模型:每個方法被執行的時候 都會建立一個“棧幀”用於儲存區域性變量表(包括引數)、操作棧、方法出口等資訊。每個方法被呼叫到執行完的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。宣告週期與執行緒相同,是執行緒私有的。
區域性變量表存放了編譯器可知的各種基本資料型別(boolean
、byte
、char
、short
、int
、float
、long
、double
)、物件引用(引用指標,並非物件本身),其中64位長度的long和double型別的資料會佔用2個區域性變數的空間,其餘資料型別只佔1個。區域性變量表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的區域性變數是完全確定的,在執行期間棧幀不會改變區域性變量表的大小空間。
3.本地方法棧
與虛擬機器棧基本類似,區別在於虛擬機器棧為虛擬機器執行的java方法服務,而本地方法棧則是為Native方法服務。
4.堆
也叫做java 堆、GC堆是java虛擬機器所管理的記憶體中最大的一塊記憶體區域,也是被各個執行緒共享的記憶體區域,在JVM啟動時建立。該記憶體區域存放了物件例項及陣列(所有new的物件)。其大小通過-Xms
-Xmx
(最大值)引數設定,-Xms為JVM啟動時申請的最小記憶體,預設為作業系統實體記憶體的1/64但小於1G,-Xmx
為JVM可申請的最大記憶體,預設為實體記憶體的1/4但小於1G,預設當空餘堆記憶體小於40%時,JVM會增大Heap到-Xmx
指定的大小,可通過-XX:MinHeapFreeRation=
來指定這個比列;當空餘堆記憶體大於70%時,JVM會減小heap的大小到-Xms指定的大小,可通過XX:MaxHeapFreeRation=
來指定這個比列,對於執行系統,為避免在執行時頻繁調整Heap的大小,通常-Xms與-Xmx的值設成一樣。由於現在收集器都是採用分代收集演算法,堆被劃分為新生代和老年代。新生代主要儲存新建立的物件和尚未進入老年代的物件。老年代儲存經過多次新生代GC(Minor GC)任然存活的物件。
新生代: 程式新建立的物件都是從新生代分配記憶體,新生代由
Eden Space
和兩塊相同大小的Survivor Space
(通常又稱S0和S1或From和To)構成,可通過-Xmn引數來指定新生代的大小,也可以通過-XX:SurvivorRation
來調整Eden Space
及Survivor Space
的大小。 老年代: 用於存放經過多次新生代GC任然存活的物件,例如快取物件,新建的物件也有可能直接進入老年代,主要有兩種情況:①.大物件,可通過啟動引數設定-XX:PretenureSizeThreshold=1024
(單位為位元組,預設為0)來代表超過多大時就不在新生代分配,而是直接在老年代分配。②.大的陣列物件,切陣列中無引用外部物件。 老年代所佔的記憶體大小為-Xmx對應的值減去-Xmn對應的值。
YoungGeneration即圖中的Eden+FromSpace+ToSpaceEden存放新生的物件SurvivorSpace有兩個,存放每次垃圾回收後存活的物件OldGenerationTenuredGeneration即圖中的OldSpace主要存放應用程式中生命週期長的存活物件
5.程式計數器
是最小的一塊記憶體區域,它的作用是當前執行緒所執行的位元組碼的行號指示器,在虛擬機器的模型裡,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、異常處理、執行緒恢復等基礎功能都需要依賴計數器完成。
三、直接記憶體
直接記憶體並不是虛擬機器記憶體的一部分,也不是Java虛擬機器規範中定義的記憶體區域。jdk1.4中新加入的NIO,引入了通道與緩衝區的IO方式,它可以呼叫Native方法直接分配堆外記憶體,這個堆外記憶體就是本機記憶體,不會影響到堆記憶體的大小。
四、Java堆記憶體的10個要點
- Java堆記憶體是作業系統分配給JVM的記憶體的一部分。
- 當我們建立物件時,它們儲存在Java堆記憶體中。
- 為了便於垃圾回收,Java堆空間分成三個區域,分別叫作New Generation, Old Generation或叫作Tenured Generation,還有Perm Space。
- 你可以通過用JVM的命令列選項 -Xms, -Xmx, -Xmn來調整Java堆空間的大小。不要忘了在大小後面加上”M”或者”G”來表示單位。舉個例子,你可以用 -Xmx256m來設定堆記憶體最大的大小為256MB。
- 你可以用JConsole或者 Runtime.maxMemory(), Runtime.totalMemory(), Runtime.freeMemory()來檢視Java中堆記憶體的大小。
- 你可以使用命令“jmap”來獲得heap dump,用“jhat”來分析heap dump。
- Java堆空間不同於棧空間,棧空間是用來儲存呼叫棧和區域性變數的。
- Java垃圾回收器是用來將死掉的物件(不再使用的物件)所佔用的記憶體回收回來,再釋放到Java堆空間中。
- 當你遇到java.lang.outOfMemoryError時,不要緊張,有時候僅僅增加堆空間就可以了,但如果經常出現的話,就要看看Java程式中是不是存在記憶體洩露了。
- 請使用Profiler和Heap dump分析工具來檢視Java堆空間,可以檢視給每個物件分配了多少記憶體。