深刻理解JVM之JVM運行時內存區域

內存區域總體設計

根據JVM規範,最初Java內存分爲5個區域,分別爲(Heap)、JVM棧(Stack)、方法區(Method Area)、本地方法棧(Native Method Stack)、程序計數器(Program Counter Register)。方法區中,還有一塊運行時常量池(Runtime Constant Pool)區域。java

在JDK1.4版本新增的NIO特性中,新增了本地內存(Native Heap),此內存區域在JVM外分配,由OS管理,此區域又叫直接內存(Direct Memory),經過ByteBuffer的靜態方法allocateDirect可在本地內存中分配直接內存Buffer,並經過JVM堆中的DirectByteBuffer對象引用此Buffer。服務器

方法區主要用於存儲類的元數據、運行時常量等數據,在JVM規範中,對於方法區的管理比較寬鬆,沒有明肯定義方法區的實現位置,JVM參考虛擬機HotSpot的實現上,把方法區放在堆的永久代中。多線程

隨着Spring等使用動態字節碼、反射、代理技術框架的流行,運行過程當中生成大量的類,方法區存儲的內容愈來愈多,致使方法區內存容易溢出,出現 java.lang.OutOfMemoryError: PremGen space異常。框架

在JDK7開始對方法區結構進行了優化拆分,並在JDK8中完全去掉了方法區。優化方法包括把類的元信息、符號引用(Symbols)移到本地內存區域,把靜態變量(class static)、字面量(internal strings)移到堆區域。其中存儲類的元信息區域叫作元空間(Metaspace)。優化

JVM內存分區.PNG

內存區域說明

區域 特徵 做用 配置參數 異常 生命週期
線程共享 類實例對象存儲空間 -Xms -Xsx -Xmn OutOfMemoryError JVM
線程私有 線程棧幀,局部變量、方法參數、操做數、動態連接等信息 -Xss(幀數) StackOverflowError OutOfMemoryError 線程
本地方法棧 線程私有 線程調用Native方法棧幀 - OutOfMemoryError 線程
程序計數器 此區域很小,爲線程私有 記錄線程運行的字節碼行號等線程運行位置信息,用於線程的切換和恢復 - - 線程
直接內存 在OS Native內存中分配,可突破JVM內存大小限制 經過相似mmap在OS內存中分配,可避免內存在OS和JVM間拷貝 - OutOfMemoryError JVM
元空間 在OS Native內存中分配 保存類的元信息、符號引用等信息 XX:MetaspaceSize XX:MaxMetaspaceSize - JVM
運行時常量池 在堆中分配 保存靜態變量、字面量等數據 - - JVM

棧Heap區域

每一個線程運行時,會在棧中分配一個線程棧,棧由棧幀(Stack Frame)組成,每次調用方法時會生成一個棧幀,方法返回時則彈出棧頂幀。棧中保存了方法入參、局部變量、操做數棧、動態連接、方法出口信息等數據。當一個線程棧幀的棧數量超過-Xss定義的幀數時,會拋出StackOverflowError異常,通常在遞歸調用時容易發生此錯誤。
JVM棧信息.PNGui

堆Stack區域

JDK8中,堆中移除了永生代區域,堆內存主要由新生代老年代兩部分組成。其中新生代由一個伊甸園(Eden)和兩個倖存者(Survivor)3部分組成,新生代的垃圾回收頻率高,Minor GC時,把Eden和其中一個Survivor中的存活對象拷貝到另外一個Survivor區,並清除前面兩個區域的數據,經過這種結構和回收方式來提升垃圾回收效率,減小內存碎片。通過若干(默認15)次後還存活的對象,將進入老年代區,當老年代數據慢是會觸發Major GCspa

JVM堆信息.PNG

老年代:新生代的內存大小默認比例爲2:1。Eden和兩個Survivor的比例爲8:1:1。內存的分配比例能夠經過 java -XX:+PrintFlagsFinal -version 命令進行查看。操作系統

[Global flags]
uintx InitialSurvivorRatio = 8
uintx NewRatio = 2

設置內存區域比例參數:線程

參數 說明
-XX:InitialSurvivorRatio 新生代Eden/Survivor空間的初始比例
-XX:Newratio 老年代和新生代的內存比例

程序計數器

當前線程所執行的行號指示器。經過改變計數器的值來肯定下一條指令,好比循環,分支,跳轉,異常處理,線程恢復等都是依賴計數器來完成。設計

Java虛擬機多線程是經過線程輪流切換並分配處理器執行時間的方式實現的。爲了線程切換能恢復到正確的位置,每條線程都須要一個獨立的程序計數器,因此它是線程私有的。

OutOfMemoryError報錯及解決方法

  1. java.lang.OutOfMemoryError:java heap space
    這種是java堆內存不夠,一個緣由是內存真不夠,另外一個緣由是程序中有死循環。若是是java堆內存不夠的話,能夠經過調整JVM下面的配置來解決:-Xms、-Xmx
  2. java.lang.OutOfMemoryError:GC overhead limit exceeded
    這是JDK6新增錯誤類型,當GC爲釋放很小空間佔用大量時間時拋出;通常是由於堆過小,致使異常的緣由,沒有足夠的內存。解決方案:

    1. 查看系統是否有使用大內存的代碼或死循環;
    2. 經過添加JVM配置,來限制使用內存:-XX:-UseGCOverheadLimit
  3. java.lang.OutOfMemoryError: PermGen space
    這一部分用於存放Class和Meta的信息,Class在被Load的時候被放入PermGen space區域。因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。這種是永久代內存不夠,可經過調整JVM的配置: -XX:MaxPermSize、-XXermSize
  4. java.lang.OutOfMemoryError: Direct buffer memory
    可能緣由是自己資源不夠或者申請的太多內存。若是不是內存泄漏的話,可使用參數-XX:MaxDirectMemorySize參數,或者-XX:MaxDirectMemorySize
  5. java.lang.OutOfMemoryError: unable to create new native thread
    可能緣由是系統內存耗盡,沒法爲新線程分配內存或者建立線程數超過了操做系統的限制。經過兩個途徑解決:

    1. 排查應用是否建立了過多的線程。經過jstack肯定應用建立了多少線程
    2. 調整操做系統線程數閾值。操做系統會限制進程容許建立的線程數,使用ulimit -u命令查看限制。某些服務器上此閾值設置的太小,好比1024。一旦應用建立超過1024個線程,就會遇到java.lang.OutOfMemoryError: unable to create new native thread問題。若是是這種狀況,能夠調大操做系統線程數閾值。
    3. 增長機器內存。若是上述兩項未能排除問題,多是正常增加的業務確實須要更多內存來建立更多線程。若是是這種狀況,增長機器內存。
    4. 減少堆內存。一個老司機也常常忽略的很是重要的知識點:線程不在堆內存上建立,線程在堆內存以外的內存上建立。因此若是分配了堆內存以後只剩下不多的可用內存,依然可能遇到java.lang.OutOfMemoryError: unable to create new native thread。考慮以下場景:系統總內存6G,堆內存分配了5G,永久代512M。在這種狀況下,JVM佔用了5.5G內存,系統進程、其餘用戶進程和線程將共用剩下的0.5G內存,頗有可能沒有足夠的可用內存建立新的線程。若是是這種狀況,考慮減少堆內存。
    5. 減少線程棧大小。線程會佔用內存,若是每一個線程都佔用更多內存,總體上將消耗更多的內存。每一個線程默認佔用內存大小取決於JVM實現。能夠利用-Xss參數限制線程內存大小,下降總內存消耗。例如,JVM默認每一個線程佔用1M內存,應用有500個線程,那麼將消耗500M內存空間。若是實際上256K內存足夠線程正常運行,配置-Xss256k,那麼500個線程將只須要消耗125M內存。(注意,若是-Xss設置的太低,將會產生java.lang.StackOverflowError錯誤)。
  6. java.lang.StackOverflowError這也內存溢出錯誤的一種,即線程棧的溢出,要麼是方法調用層次過多(好比存在無限遞歸調用),要麼是線程棧過小。能夠經過優化程序設計,減小方法調用層次;調整-Xss參數增長線程棧大小。
相關文章
相關標籤/搜索