注意:本系列博客,主要參考自如下四本書html
《分佈式Java應用:基礎與實踐》《深刻理解Java虛擬機(第二版)》《深刻分析Java web技術內幕》《實戰java虛擬機》前端
一、爲何要了解JVM內存管理機制java
- JVM自動的管理內存的分配與回收,這會在不知不覺中浪費不少內存,致使JVM花費不少時間去進行垃圾回收(GC)
- 內存泄露,致使JVM內存最終不夠用
二、JVM內存結構linux
![](http://static.javashuo.com/static/loading.gif)
根據上圖,JVM內存結構包括:git
- 方法區(也就是"持久代"),java8裏完全被移除,取而代之的是元數據區
- 堆
- 棧(在hotspot JVM中,JVM方法棧--Java虛擬棧,與本地方法棧是同一個)
- PC寄存器(程序計數器)
還有一塊:web
- 直接內存:直接向系統內存申請的一塊內存區域,javaNIO會使用,速度優於java堆內存。- 隸屬於物理內存,不屬於JVM內存
注意點:spring
- 堆是GC的主要區域,方法區、直接內存也會發生GC
- 棧與PC寄存器是每一個線程都會建立的私有區域,不會GC
- 直接內存使用速度因爲堆內存,可是內存的申請速度低於堆內存
2.一、方法區docker
- 存放內容(類的信息、類static屬性、方法、常量池)
- 已經加載的類的信息(名稱、修飾符等)
- 類中的static變量
- 類中的field信息
- 類中定義爲final常量
- 類中的方法信息
- 運行時常量池:編譯器生成的各類字面量和符號引用(編譯期)存儲在class文件的常量池中,這部份內容會在類加載以後進入運行時常量池,class文件的常量池查看 第三章 類文件結構與javap的使用
- 使用實例:反射,在程序中經過Class對象調用getName等方法獲取信息數據時,這些信息數據來源於方法區。
- 調節參數
- -XX:PermSize:指定方法區的最小值,默認爲16M
- -XX:MaxPermSize:指定方法區的最大值,默認爲64M
- 所拋錯誤
- 方法區域要使用的內存超過了其容許的大小時,拋出OutOfMemoryError
- 內存回收的主要目標
- 對類的卸載(這也是爲何不少企業使用velocity等模板引擎作前端而不是使用jsp的緣由之一)
- 針對常量池的回收
- 總結
- 通常而言,在企業開發中,-XX:PermSize==-XX:MaxPermSize
- 一般,這個大小設置爲256M就沒問題了,固然還要根據本身的程序去預估,並在運行過程當中去調整,這裏以在Resin服務器中配置爲例
![](http://static.javashuo.com/static/loading.gif)
View Code
- 類中的static變量會在方法區分配內存,可是類中的實例變量不會(類中的實例變量會隨着對象實例的建立一塊兒分配在堆中,固然如果基本數據類型的話,會隨着對象的建立直接壓入操做數棧)
- 關於方法區的存放內容,能夠這樣去想全部的經過Class對象能夠反射獲取的都是從方法區獲取的(包括Class對象也是方法區的,Class是該類下全部其餘信息的訪問入口)
注意:常量池在jdk1.6在方法區;在jdk1.7在堆json
附:元數據區數組
- 調節參數:-XX:MaxMetaspaceSize,若是不指定大小,極限狀況下可能耗盡系統全部內存
- 元數據區是堆外的一塊直接內存
2.二、堆
- 存放內容
- 對象實例(類中的實例變量會隨着對象實例的建立一塊兒分配在堆中,固然如果基本數據類型的話,會隨着對象的建立直接壓入操做數棧),這一點查看 第四章 類加載機制
- 數組值
- 使用實例
- 全部經過new建立的對象都在這塊兒內存分配,具體分配到年輕代仍是年老代須要根據配置參數而定(新建對象直接分配到年老代有兩種狀況,看下邊)
- 調節參數
- -Xmx:最大堆內存,默認爲物理內存的1/4但小於1G
- -Xms:最小堆內存,默認爲物理內存的1/64但小於1G
- -XX:MinHeapFreeRatio,默認當空餘堆內存小於最小堆內存的40%時,堆內存增大到-Xmx
- -XX:MaxHeapFreeRatio,當空餘堆內存大於最大堆內存的70%時,堆內存減少到-Xms
- 注意點
- 在實際使用中,-Xmx與-Xms配置成相等的,這樣,堆內存就不會頻繁的進行調整了
- 拋出錯誤
- OutOfMemoryError:在堆中沒有內存完成實例分配(關於實例內存的分配,以後再說),此時堆內存已達到最大沒法擴展時。
- 堆內存劃分
![](http://static.javashuo.com/static/loading.gif)
-
- 新生代
- 組成:Eden+From(S0)+To(S1)
- -Xmn:整個新生代的大小
- -XX:SurvivorRatio:調整Eden:From(To)的比率,默認爲8:1
- 年老代
- 新建對象直接分配到年老代,兩種狀況
- 大對象:-XX:PretenureSizeThreshold(單位:字節)參數來指定大對象的標準,在Parallel Scavenge GC下可能無效,具體見《第五章 JVM垃圾收集器(1) 》
- 大數組:數組中的元素沒有引用任何外部的對象
- 總結
2.三、棧
- 注意點
- 每條線程都會分配一個棧,每一個棧中有多個棧幀(每個方法對應一個棧幀)
- 線程建立的時候建立一個線程的java棧
- 每一個方法在執行的同時都會建立一個棧幀,每一個棧幀用於存儲當前方法的局部變量表、操做數棧等,具體查看本文第一個圖,每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程,說的更明白一點,就是方法執行時建立棧幀,方法結束時釋放棧幀所佔內存
- 存放內容
- 局部變量表:八大基本數據類型數據、對象引用。該空間在編譯期已經分配好,運行期不變。
- 操做數棧:是執行引擎直接操做的部分
- 調節參數
- -Xss:設置棧的大小,一般設置爲1m就好
![](http://static.javashuo.com/static/loading.gif)
View Code
- 支持native方法執行(本地方法棧)
- 所拋錯誤
- StackOverFlowError:線程請求的棧深度大於虛擬機所容許的深度。
- 棧的深度就是方法調用嵌套的層數,受限於-Xss的大小
- 典型場景:沒有終止條件的遞歸(遞歸基於棧)。
- 每一個方法的棧的深度在javac編譯以後就已經肯定了,查看 第三章 類文件結構與javap的使用
- OutOfMemoryError:虛擬機棧能夠動態擴展,若是擴展的時候沒法申請到足夠的內存。
- 須要注意的是,棧能夠動態擴展,可是棧中的局部變量表不能夠。
2.四、PC寄存器(程序計數器)
- 概念:當前線程所執行的字節碼的行號指示器,用於字節碼解釋器對字節碼指令的執行。
- 多線程:經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個時刻,一個處理器(也就是一個核)只能執行一條線程中的指令,爲了線程切換後能恢復到正確的執行位置,每條線程都要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲。
附:對象分配(《實戰java虛擬機》)
![](http://static.javashuo.com/static/loading.gif)