JVM堆內存和非堆內存

JVM堆內存和非堆內存

目錄html

堆和非堆內存

按照官方的說法:「Java 虛擬機具備一個堆(Heap),堆是運行時數據區域,全部類實例和數組的內存均今後處分配。堆是在 Java 虛擬機啓動時建立的。」「在JVM中堆以外的內存稱爲非堆內存(Non-heap memory)」。java

JVM主要管理兩種類型的內存:堆和非堆。linux

Heap memory Code Cache
Eden Space
Survivor Space
Tenured Gen
non-heap memory Perm Gen
native heap?(I guess)
  • 堆內存

Java 虛擬機具備一個堆,堆是運行時數據區域,全部類實例和數組的內存均今後處分配。堆是在 Java 虛擬機啓動時建立的。對象的堆內存由稱爲垃圾回收器的自動內存管理系統回收。web

堆的大小能夠固定,也能夠擴大和縮小。堆的內存不須要是連續空間。算法

  • 非堆內存

Java 虛擬機管理堆以外的內存(稱爲非堆內存)。數組

Java 虛擬機具備一個由全部線程共享的方法區。方法區屬於非堆內存。它存儲每一個類結構,如運行時常數池、字段和方法數據,以及方法和構造方法的代碼。它是在 Java 虛擬機啓動時建立的。tomcat

方法區在邏輯上屬於堆,但 Java 虛擬機實現能夠選擇不對其進行回收或壓縮。與堆相似,方法區的大小能夠固定,也能夠擴大和縮小。方法區的內存不須要是連續空間。服務器

除了方法區外,Java 虛擬機實現可能須要用於內部處理或優化的內存,這種內存也是非堆內存。例如,JIT 編譯器須要內存來存儲從 Java 虛擬機代碼轉換而來的本機代碼,從而得到高性能。jvm

  • 幾個基本概念

PermGen space:全稱是Permanent Generation space,即永久代。就是說是永久保存的區域,用於存放Class和Meta信息,Class在被Load的時候被放入該區域,GC(Garbage Collection)應該不會對PermGen space進行清理,因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。工具

Heap space:存放Instance。

Java Heap分爲3個區,Young即新生代,Old即老生代和Permanent。

Young保存剛實例化的對象。當該區被填滿時,GC會將對象移到Old區。Permanent區則負責保存反射對象。

  • 堆內存分配
    • JVM初始分配的堆內存由-Xms指定,默認是物理內存的1/64;
    • JVM最大分配的堆內存由-Xmx指定,默認是物理內存的1/4。
    • 默認空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制;
    • 空餘堆內存大於70%時,JVM會減小堆直到-Xms的最小限制。
    • 所以服務器通常設置-Xms、-Xmx 相等以免在每次GC 後調整堆的大小。
    • 說明:若是-Xmx 不指定或者指定偏小,應用可能會致使java.lang.OutOfMemory錯誤,此錯誤來自JVM,不是Throwable的,沒法用try...catch捕捉。
  • 非堆內存分配
    • JVM使用-XX:PermSize設置非堆內存初始值,默認是物理內存的1/64;
    • 由XX:MaxPermSize設置最大非堆內存的大小,默認是物理內存的1/4。
      • 還有一說:MaxPermSize缺省值和-server -client選項相關,-server選項下默認MaxPermSize爲64m,-client選項下默認MaxPermSize爲32m。這個我沒有實驗。
    • XX:MaxPermSize設置太小會致使java.lang.OutOfMemoryError: PermGen space 就是內存益出。
    • 爲何會內存益出:
      1. 這一部份內存用於存放Class和Meta的信息,Class在被 Load的時候被放入PermGen space區域,它和存放Instance的Heap區域不一樣。
      2. GC(Garbage Collection)不會在主程序運行期對PermGen space進行清理,因此若是你的APP會LOAD不少CLASS 的話,就極可能出現PermGen space錯誤。
    • 這種錯誤常見在web服務器對JSP進行pre compile的時候。

JVM內存限制(最大值)

  • 首先JVM內存限制於實際的最大物理內存,假設物理內存無限大的話,JVM內存的最大值跟操做系統有很大的關係。簡單的說就32位處理器雖然可控內存空間有4GB,可是具體的操做系統會給一個限制,這個限制通常是2GB-3GB(通常來講Windows系統下爲1.5G-2G,Linux系統下爲2G-3G),而64bit以上的處理器就不會有限制了。
  • 爲何有的機器我將-Xmx和-XX:MaxPermSize都設置爲512M以後Eclipse能夠啓動,而有些機器沒法啓動?
    • 經過上面對JVM內存管理的介紹咱們已經瞭解到JVM內存包含兩種:堆內存和非堆內存,另外JVM最大內存首先取決於實際的物理內存和操做系統。因此說設置VM參數致使程序沒法啓動主要有如下幾種緣由:
      1. 參數中-Xms的值大於-Xmx,或者-XX:PermSize的值大於-XX:MaxPermSize;
      2. -Xmx的值和-XX:MaxPermSize的總和超過了JVM內存的最大限制,好比當前操做系統最大內存限制,或者實際的物理內存等等。說到實際物理內存這裏須要說明一點的是,若是你的內存是1024MB,但實際系統中用到的並不多是1024MB,由於有一部分被硬件佔用了。
  • 若是你有一個雙核的CPU,也許能夠嘗試這個參數: -XX:+UseParallelGC 讓GC能夠更快的執行。(只是JDK 5裏對GC新增長的參數)
  • 若是你的WEB APP下都用了大量的第三方jar,其大小超過了服務器jvm默認的大小,那麼就會產生內存益出問題了。解決方法: 設置MaxPermSize大小。
    • 增長服務器啓動的JVM參數設置: -Xms128m -Xmx256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m
    • 如tomcat,修改TOMCAT_HOME/bin/catalina.sh,在echo "Using CATALINA_BASE: $CATALINA_BASE"上面加入如下行:JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128m
  • 建議:將相同的第三方jar文件移置到tomcat/shared/lib目錄下,這樣能夠減小jar 文檔重複佔用內存

JVM內存設置參數

  • 內存設置參數
設置項 說明
-Xms512m 表示JVM初始分配的堆內存大小爲512m(JVM Heap(堆內存)最小尺寸,初始分配)
-Xmx1024m JVM最大容許分配的堆內存大小爲1024m,按需分配(JVM Heap(堆內存)最大容許的尺寸,按需分配)
-XX:PermSize=512M JVM初始分配的非堆內存
-XX:MaxPermSize=1024M JVM最大容許分配的非堆內存,按需分配
-XX:NewSize/-XX:MaxNewSize 定義YOUNG段的尺寸,NewSize爲JVM啓動時YOUNG的內存大小;
MaxNewSize爲最大可佔用的YOUNG內存大小。
-XX:SurvivorRatio 設置YOUNG代中Survivor空間和Eden空間的比例
  • 說明:
    1. 若是-Xmx不指定或者指定偏小,應用可能會致使java.lang.OutOfMemory錯誤,此錯誤來自JVM不是Throwable的,沒法用try...catch捕捉。
    2. PermSize和MaxPermSize指明虛擬機爲java永久生成對象(Permanate generation)如,class對象、方法對象這些可反射(reflective)對象分配內存限制,這些內存不包括在Heap(堆內存)區之中。
    3. -XX:MaxPermSize分配太小會致使:java.lang.OutOfMemoryError: PermGen space。
    4. MaxPermSize缺省值和-server -client選項相關:-server選項下默認MaxPermSize爲64m、-client選項下默認MaxPermSize爲32m。
  • 申請一塊內存的過程
    1. JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域
    2. 當Eden空間足夠時,內存申請結束。不然到下一步
    3. JVM試圖釋放在Eden中全部不活躍的對象(這屬於1或更高級的垃圾回收);釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區/OLD區
    4. Survivor區被用來做爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,不然會被保留在Survivor區
    5. 當OLD區空間不夠時,JVM會在OLD區進行徹底的垃圾收集(0級)
    6. 徹底垃圾收集後,若Survivor及OLD區仍然沒法存放從Eden複製過來的部分對象,致使JVM沒法在Eden區爲新對象建立內存區域,則出現」out of memory錯誤」
  • resin服務器典型的響應時間優先型的jvm配置:
    -Xmx2000M -Xms2000M -Xmn500M
    -XX:PermSize=250M -XX:MaxPermSize=250M
    -Xss256K
    -XX:+DisableExplicitGC
    -XX:SurvivorRatio=1
    -XX:+UseConcMarkSweepGC
    -XX:+UseParNewGC
    -XX:+CMSParallelRemarkEnabled
    -XX:+UseCMSCompactAtFullCollection
    -XX:CMSFullGCsBeforeCompaction=0
    -XX:+CMSClassUnloadingEnabled
    -XX:LargePageSizeInBytes=128M
    -XX:+UseFastAccessorMethods
    -XX:+UseCMSInitiatingOccupancyOnly
    -XX:CMSInitiatingOccupancyFraction=60
    -XX:SoftRefLRUPolicyMSPerMB=0
    -XX:+PrintClassHistogram
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -XX:+PrintHeapAtGC
    -Xloggc:log/gc.log

內存回收算法

Java中有四種不一樣的回收算法,對應的啓動參數爲:

–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC

Serial Collector

大部分平臺或者強制 java -client 默認會使用這種。

young generation算法 = serial

old generation算法 = serial (mark-sweep-compact)

這種方法的缺點很明顯, stop-the-world, 速度慢。服務器應用不推薦使用。

Parallel Collector

在linux x64上默認是這種,其餘平臺要加 java -server 參數纔會默認選用這種。

young = parallel,多個thread同時copy

old = mark-sweep-compact = 1

優勢:新生代回收更快。由於系統大部分時間作的gc都是新生代的,這樣提升了throughput(cpu用於非gc時間)

缺點:當運行在8G/16G server上old generation live object太多時候pause time過長

Parallel Compact Collector (ParallelOld)

young = parallel = 2

old = parallel,分紅多個獨立的單元,若是單元中live object少則回收,多則跳過

優勢:old old generation上性能較 parallel 方式有提升

缺點:大部分server系統old generation內存佔用會達到60%-80%, 沒有那麼多理想的單元live object不多方便迅速回收,同時compact方面開銷比起parallel並沒明顯減小。

Concurrent Mark-Sweep(CMS) Collector

young generation = parallel collector = 2

old = cms

同時不作 compact 操做。

優勢:pause time會下降, pause敏感但CPU有空閒的場景須要建議使用策略4.

缺點:cpu佔用過多,cpu密集型服務器不適合。另外碎片太多,每一個object的存儲都要經過鏈表連續跳n個地方,空間浪費問題也會增大。

內存監控方法

  • jmap -heap 查看java 堆(heap)使用狀況
  • jmap -heap pid 
     
    using thread-local object allocation.
     
    Parallel GC with 4 thread(s)   #GC 方式
     
    Heap Configuration:  #堆內存初始化配置
     
    MinHeapFreeRatio=40  #對應jvm啓動參數-XX:MinHeapFreeRatio設置JVM堆最小空閒比率(default 40)
    MaxHeapFreeRatio=70  #對應jvm啓動參數 -XX:MaxHeapFreeRatio設置JVM堆最大空閒比率(default 70)
    MaxHeapSize=512.0MB  #對應jvm啓動參數-XX:MaxHeapSize=設置JVM堆的最大大小
    NewSize  = 1.0MB     #對應jvm啓動參數-XX:NewSize=設置JVM堆的‘新生代’的默認大小
    MaxNewSize =4095MB   #對應jvm啓動參數-XX:MaxNewSize=設置JVM堆的‘新生代’的最大大小
    OldSize  = 4.0MB     #對應jvm啓動參數-XX:OldSize=<value>:設置JVM堆的‘老生代’的大小
    NewRatio  = 8        #對應jvm啓動參數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
    SurvivorRatio = 8    #對應jvm啓動參數-XX:SurvivorRatio=設置年輕代中Eden區與Survivor區的大小比值
    PermSize= 16.0MB     #對應jvm啓動參數-XX:PermSize=<value>:設置JVM堆的‘永生代’的初始大小
    MaxPermSize=64.0MB   #對應jvm啓動參數-XX:MaxPermSize=<value>:設置JVM堆的‘永生代’的最大大小
     
    Heap Usage:          #堆內存分步
     
    PS Young Generation
     
    Eden Space:         #Eden區內存分佈
     
    capacity = 20381696 (19.4375MB)             #Eden區總容量
    used     = 20370032 (19.426376342773438MB)  #Eden區已使用
    free     = 11664 (0.0111236572265625MB)     #Eden區剩餘容量
    99.94277218147106% used                     #Eden區使用比率
     
    From Space:        #其中一個Survivor區的內存分佈
     
    capacity = 8519680 (8.125MB)
    used     = 32768 (0.03125MB)
    free     = 8486912 (8.09375MB)
    0.38461538461538464% used
     
    To Space:          #另外一個Survivor區的內存分佈
     
    capacity = 9306112 (8.875MB)
    used     = 0 (0.0MB)
    free     = 9306112 (8.875MB)
    0.0% used
     
    PS Old Generation  #當前的Old區內存分佈
     
    capacity = 366280704 (349.3125MB)
    used     = 322179848 (307.25464630126953MB)
    free     = 44100856 (42.05785369873047MB)
    87.95982001825573% used
     
    PS Perm Generation #當前的 「永生代」 內存分佈
     
    capacity = 32243712 (30.75MB)
    used     = 28918584 (27.57891082763672MB)
    free     = 3325128 (3.1710891723632812MB)
    89.68751488662348% used

     

  • JVM內存監控工具

  • <%@ page import="java.lang.management.*" %>
    <%@ page import="java.util.*" %>
    <html>
    <head>
      <title>JVM Memory Monitor</title>
    </head>
    <body>
    <table border="0" width="100%">
        <tr><td colspan="2" align="center"><h3>Memory MXBean</h3></td></tr>
        <tr><td width="200">Heap Memory Usage</td><td><%=ManagementFactory.getMemoryMXBean().getHeapMemoryUsage()%></td></tr>
        <tr><td>Non-Heap Memory Usage</td><td><%=ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage()%></td></tr>
        <tr><td colspan="2">&nbsp;</td></tr>
        <tr><td colspan="2" align="center"><h3>Memory Pool MXBeans</h3></td></tr>
    <%
            Iterator iter = ManagementFactory.getMemoryPoolMXBeans().iterator();
            while (iter.hasNext()) {
                MemoryPoolMXBean item = (MemoryPoolMXBean) iter.next();
    %>
    <tr><td colspan="2">
        <table border="0" width="100%" style="border: 1px #98AAB1 solid;">
            <tr><td colspan="2" align="center"><b><%= item.getName() %></b></td></tr>
            <tr><td width="200">Type</td><td><%= item.getType() %></td></tr>
            <tr><td>Usage</td><td><%= item.getUsage() %></td></tr>
            <tr><td>Peak Usage</td><td><%= item.getPeakUsage() %></td></tr>
            <tr><td>Collection Usage</td><td><%= item.getCollectionUsage() %></td></tr>
        </table>
    </td></tr>
    <tr><td colspan="2">&nbsp;</td></tr>
    <%} %>
    </table>
    </body>
    </html>
相關文章
相關標籤/搜索