JAVA GC(Garbage Collection)及OOM那些事

JAVA運行時內存區域

java虛擬機內存分區 

    程序計數器

    一塊很小的內存空間html

  • 當前線程所執行的字節碼的行號指示器
  • 當前線程私有
  • 不會出現OutOfMemoryError狀況

java虛擬機棧

      一般存放基本數據類型,對象引用(一個指向對象起始地址的引用指針或一個表明對象的句柄),reeturnAddress類型(指向一條字節碼指令的地址)java

  • 線程私有,生命週期與線程相同
  • java方法執行的內存模型,每一個方法執行的同時都會建立一個棧幀,存儲局部變量表(基本類型、對象引用)、操做數棧、動態連接、方法出口等信息
  • StackOverflowError異常:當線程請求的棧深度大於虛擬機所容許的深度
  • OutOfMemoryError異常:若是棧的擴展時沒法申請到足夠的內存

本地方法棧

        與虛擬機棧類似,主要爲虛擬機使用到的Native方法服務,在HotSpot虛擬機中直接把本地方法棧與虛擬機棧二合一linux

        和虛擬機棧同樣可能拋出StackOverflowError和OutOfMemoryError異常。算法

Java堆(Java Heap)

        java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此區域的惟一目的就是存儲對象實例。java堆是垃圾收集器管理的主要區域。java堆還能夠細分爲:新生代與老年代。再細一點有Eden空間、Form Survivor空間、To Survivor空間等。數據庫

  • 能夠經過-Xmx和-Xms控制堆的大小
  • OutOfMemoryError異常:當在堆中沒有內存完成實例分配,且堆也沒法再擴展時。

方法區

  • 線程間共享
  • 用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據
  • OutOfMemoryError異常:當方法區沒法知足內存的分配需求時

運行時常量池

  • 方法區的一部分
  • 用於存放編譯期生成的各類字面量與符號引用
  • OutOfMemoryError異常:當常量池沒法再申請到內存時

直接內存

    直接內存並非虛擬機運行時數據區的一部分,也不是java虛擬機規範中定義的內存區域,是jvm外部的內存區域,這部分區域也可能致使OutOfMemoryError異常。ubuntu

  • NIO可使用Native函數庫直接分配堆外內存,堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做
  • 大小不受Java堆大小的限制,受本機(服務器)內存限制
  • OutOfMemoryError異常:系統內存不足時

Java GC(Garbage Collection)

GC基本回收算法

其實這些算法和OOM沒有太大關係,OOM產生的緣由很簡單,就是須要內存的時候沒有內存了,可是對垃圾回收機制的理解可讓咱們知道-Xmx  -Xms Xmn – MaxPermSize參數該怎麼樣設置,避免頻繁full-GC(Full-GC會形成app短暫停頓,此時app是不會響應任何客戶端請求的),而且合理設置參數.(1對於緩存過多的系統能夠增大-Xmx,經過減少Xmn調整新生代年老代比例,2對於瞬時對象過多的系統,年老代的heap能夠不用分配那麼大)緩存

  1. 引用計數(Reference Counting)
    比較古老的回收算法。原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。垃圾回收時,只用收集計數爲0的對象。此算法最致命的是沒法處理循環引用的問題。
  2. 標記-清除(Mark-Sweep)
    此算法執行分兩階段。第一階段從引用根節點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時,會產生內存碎片。
  3. 複製(Copying)
    此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。此算法每次只處理正在使用中的對象,所以複製成本比較小,同時複製過去之後還能進行相應的內存整理,不會出現「碎片」問題。固然,此算法的缺點也是很明顯的,就是須要兩倍內存空間。
  4. 標記-整理(Mark-Compact)
    此算法結合了「標記-清除」和「複製」兩個算法的優勢。也是分兩階段,第一階段從根節點開始標記全部被引用對象,第二階段遍歷整個堆,把清除未標記對象而且把存活對象「壓縮」到堆的其中一塊,按順序排放。此算法避免了「標記-清除」的碎片問題,同時也避免了「複製」算法的空間問題。
  5. 分代(Generational Collecting)
    基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法(上述方式中的一個)進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。

GC垃圾回收器

        HotSpot JVM一共有4個垃圾回收器:Serial(串行)、Parallel / Throughput(並行)、CMS(併發)、and the new kid on the block G1(G1)。HotSpot默認使用Parallel / Throughput回收器,但它經常不是你運行程序的最佳選擇。好比CMS和G1會使GC停頓(GC pause)發生的頻率下降,可是對於每次停頓所花費的時間,極可能比Parallel回收器更長。在使用相同大小堆內存的狀況下,Parallel回收器能帶來更高的吞吐量。因此,須要根據可接受的GC停頓頻率和持續時間,選擇合適的垃圾回收器。

  GC算法自己能夠是串行的(單線程),也能夠是並行的(多線程)。所以當咱們提到併發的GC時,並不表明它是並行完成的,相反當提到串行GC時,也並不意味着就必定會出現GC停頓。在GC的世界中,併發和並行是兩個徹底不一樣的概念。併發針對的是GC週期,而並行針對GC算法自身。tomcat

        Java 7中引入了G1回收器,它是JVM垃圾回收器中最新的組件。G1最大的優點就是解決了CMS中常見的內存碎片問題:GC週期會從老年代(Old Generation)中釋放內存塊,結果內存變得像瑞士奶酪那樣千瘡百孔,直到JVM對其無從下手了,纔不得不停下來處理這些碎片。可是某些狀況下其餘回收器可能比G1有更好的表現,這徹底取決於你的需求(能夠經過GC日誌分析服務器

分代垃圾回收詳述


如上圖所示,爲Java堆中的各代分佈。多線程

  1. Young(年輕代)複製算法
    年輕代分三個區。一個Eden區,兩個Survivor區。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被複制到另一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的而且此時還存活的對象,將被複制「年老區(Tenured)」。須要注意,Survivor的兩個區是對稱的,沒前後關係,因此同一個區中可能同時存在從Eden複製過來 對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。並且,Survivor區總有一個是空的。
  2. Tenured(年老代)標記清除|標記整理
    年老代存放從年輕代存活的對象。通常來講年老代存放的都是生命期較長的對象。
  3. Perm(持久代)很難發生GC
    用於存放靜態文件,現在Java類、方法等。持久代對垃圾回收沒有顯著影響,可是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類。持久代大小經過-XX:MaxPermSize=進行設置。
  4. OOM致使的緣由:Full GC後,發現剩餘的(heap,方法區)空間仍是不夠程序使用,這樣就會致使OOM,  能致使Full GC:
    1. Tenured被寫滿
    2. Perm域被寫滿
    3. System.gc()被顯示調用
    4. 上一次GC以後Heap的各域分配策略動態變化

常見內存溢出錯誤

OutOfMemoryError異常

        除了程序計數器外,虛擬機內存的其餘幾個運行時區域都有發生OutOfMemoryError(OOM)異常的可能,

        Java Heap 溢出

        通常的異常信息:java.lang.OutOfMemoryError:Java heap spacess

        java堆用於存儲對象實例,咱們只要不斷的建立對象,而且保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,就會在對象數量達到最大堆容量限制後產生內存溢出異常。

        出現這種異常,通常手段是先經過內存映像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉存快照進行分析,重點是確認內存中的對象是不是必要的,先分清是由於內存泄漏(Memory Leak)仍是內存溢出(Memory Overflow)。

        若是是內存泄漏,可進一步經過工具查看泄漏對象到GC Roots的引用鏈。因而就能找到泄漏對象時經過怎樣的路徑與GC Roots相關聯並致使垃圾收集器沒法自動回收。

        若是不存在泄漏,那就應該檢查虛擬機的參數(-Xmx與-Xms)的設置是否適當。

虛擬機棧和本地方法棧溢出

        若是線程請求的棧深度大於虛擬機所容許的最大深度,將拋出StackOverflowError異常。

        若是虛擬機在擴展棧時沒法申請到足夠的內存空間,則拋出OutOfMemoryError異常

        這裏須要注意當棧的大小越大可分配的線程數就越少。

運行時常量池溢出

        異常信息:java.lang.OutOfMemoryError:PermGen space

        若是要向運行時常量池中添加內容,最簡單的作法就是使用String.intern()這個Native方法。該方法的做用是:若是池中已經包含一個等於此String的字符串,則返回表明池中這個字符串的String對象;不然,將此String對象包含的字符串添加到常量池中,而且返回此String對象的引用。因爲常量池分配在方法區內,咱們能夠經過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量。

方法區溢出

        方法區用於存放Class的相關信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。

        異常信息:java.lang.OutOfMemoryError:PermGen space

        方法區溢出也是一種常見的內存溢出異常,一個類若是要被垃圾收集器回收,斷定條件是很苛刻的。在常常動態生成大量Class的應用中,要特別注意這點

OOM異常的解決思路

生成Dump快照文件:

  • 經過jvm參數—XX:-HeapDumpOnOutOfMemoryError可讓JVM在出現內存溢出是Dump出當前的內存轉儲快照
  • 用jmap生產dump文件,win經過任務管理器查看tomcat的進程pid,linux用ps命令查看進程pid,而後用jmap命令 

先經過內存映像分析工具(如Eclipse的Memory Analyzer)進行分析,常見的狀況有:

  • 內存泄露,對象已經死了,沒法經過垃圾收集器進行自動回收,經過找出泄露的代碼位置和緣由,纔好肯定解決方案;
  • 內存溢出,內存中的對象都還必須存活着,這說明Java堆分配空間不足,檢查堆設置大小(-Xmx與-Xms),檢查代碼是否存在對象生命週期太長、持有狀態時間過長的狀況。

利用jmap分析CPU利用率高的緣由

    1 先採用jps 或ps -ef|grep xxx找到 JAVA 程序的PID

    2 利用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid 找到佔用CPU最高的的線程列表

    如 ps -mp 2633 -o THREAD,tid,time | sort -rn |head -10

    3 將線程ID轉化爲十六進制  printf "%x\n" 21742,好比獲得: aaee

    4 打印線程的堆棧信息    jstack 2633 |grep e18 -A 30   若是運行在64位JVM上,可能須要指定-J-d64命令選項參數。

     5 根據提示,調整代碼,從新發布程序

使用 jmap(Memory Map)和jhat(Java Heap Analysis Tool)查看堆棧狀況

    1 jmap -permstat pid

    打印進程的類加載器和類加載器加載的持久代對象信息,輸出:類加載器名稱、對象是否存活(不可靠)、對象地址、父類加載器、已加載的類大小等信息

    2 jmap -heap pid

    使用jmap -heap pid查看進程堆內存使用狀況,包括使用的GC算法、堆配置參數和各代中堆內存使用狀況

    3 jmap -histo[:live] pid

    使用jmap -histo[:live] pid查看堆內存中的對象數目、大小統計直方圖,若是帶上live則只統計活對象

    4 jmap -dump:format=b,file=dumpFileName pid 

        用jmap把進程內存使用狀況dump到文件中,再用jhat分析查看。jmap進行dump命令格式以下 jmap -dump:format=b,file=/tmp/dump.dat 21711 

    注意若是Dump文件太大,可能須要加上-J-Xmx512m這種參數指定最大堆內存,即jhat -J-Xmx512m -port 9998 /tmp/dump.dat

jstat(JVM統計監測工具)

    jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]

    vmid是Java虛擬機ID,在Linux/Unix系統上通常就是進程ID。interval是採樣時間間隔。count是採樣數目。好比下面輸出的是GC信息,採樣時間間隔爲250ms,採樣數爲4:

    jstat -gc pid 60000 60

    root@ubuntu:/# jstat -gc 21711 250 4  S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT    192.0  192.0   64.0   0.0    6144.0   1854.9   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649 192.0  192.0   64.0   0.0    6144.0   1972.2   32000.0     4111.6   55296.0 25472.7    702    0.431   3      0.218    0.649

    各列的含義:

    S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used) EC、EU:Eden區容量和使用量 OC、OU:年老代容量和使用量 PC、PU:永久代容量和使用量 YGC、YGT:年輕代GC次數和GC耗時 FGC、FGCT:Full GC次數和Full GC耗時 GCT:GC總耗時

若是分析結果代表執行GC的時間只有0.1-0.3秒,那你就不必浪費時間去進行GC優化。可是,若是GC的執行時間是1-3秒,或者超過10秒,GC將勢在必行。

hprof(Heap/CPU Profiling Tool)

         hprof可以展示CPU使用率,統計堆內存使用狀況。

         不多使用,常用java core來分析

OOM產生的文件及分析工具

    Java程序運行時,有時會產生JavaCore及HeapDump文件,它通常發生於Java程序遇到致命問題的狀況下。

    爲了可以保留Java應用發生致命錯誤前的運行狀態,JVM在死掉前產生兩個文件,分別爲JavaCore及HeapDump文件。

HeapDump:內存使用狀況

     可使用IBM的HeapAnalyzer工具分析

    HeapDump文件是一個二進制文件,它保存了某一時刻JVM堆中對象使用狀況,這種文件須要相應的工具進行分析,如IBM Heap Analyzer這類工具。這類文件最重要的做用就是分析系統中是否存在內存溢出的狀況。

JavaCore:CPU使用狀況

    可使用IBM的jca工具分析

    一般狀況下,頻繁發生core dump是因爲如下兩類緣由致使: 內存泄漏、內存碎片的問題

    JavaCore文件主要保存的是Java應用各線程在某一時刻的運行的位置,即JVM執行到哪個類、哪個方法、哪個行上。它是一個文本文件,打開後能夠看到每個線程的執行棧,以stack trace的顯示。經過對JavaCore文件的分析能夠獲得應用是否「卡」在某一點上,即在某一點運行的時間太長,例如數據庫查詢,長期得不到響應,最終致使系統崩潰等狀況。

native_stderr.log:詳細垃圾回收信息

      可使用IBM的ga(IBM Pattern Modeling and Analysis Tool for Java Garbage Collector)  工具分析

    只要在WebSphere管理控制檯的java進程屬性裏勾選「詳細垃圾回收」

影響GC性能的參數

GC優化的最基本原則是將不一樣的GC參數用於2臺或者多臺服務器,並進行對比,並將那些被證實提升了性能或者減小了GC執行時間的參數應用於服務器。請謹記這一點。

GC優化須要考慮的Java參數

定義

參數

描述

堆內存空間

-Xms

Heap area size when starting JVM

啓動JVM時的堆內存空間。

 

-Xmx

Maximum heap area size

堆內存最大限制

新生代空間

-XX:NewRatio

Ratio of New area and Old area

新生代和老年代的佔比

 

-XX:NewSize

New area size

新生代空間

 

-XX:SurvivorRatio

Ratio ofEdenarea and Survivor area

伊甸園空間和倖存者空間的佔比

我在進行GC優化時常用-Xms,-Xmx和-XX:NewRatio。-Xms和-Xmx是必須的。你如何設定NewRatio 會對GC性能產生十分顯著的影響。有些人可能會問如何設定Perm區域的大小?你能夠經過-XX:PermSize 和-XX:MaxPermSize參數來設定,

當OutOfMemoryError 錯誤發生而且是因爲Perm空間不足致使時,另外一個可能影響GC性能的參數是GC類型。下表列出了全部可選的GC類型(基於JDK6.0)

GC類型可選參數

分類

參數

備考

Serial GC

-XX:+UseSerialGC

 

Parallel GC

-XX:+UseParallelGC
-XX:ParallelGCThreads=value

 

Parallel Compacting GC

-XX:+UseParallelOldGC

 

CMS GC

-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+CMSParallelRemarkEnabled
-XX:CMSInitiatingOccupancyFraction=value
-XX:+UseCMSInitiatingOccupancyOnly

 

G1

-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC

在JDK6中這兩個參數必須同時使用

除了G1 GC,能夠經過每種類型第一行的參數來切換GC類型。最經常使用的GC類型是Serial GC。他專門針對客戶端系統進行了優化。

影響GC性能的參數有不少,可是上面提到的參數會帶來最顯著的效果。請牢記,設定過多的參數不必定會減小GC執行時間

參考:

成爲Java GC專家—如何優化Java垃圾回收機制的系列文章

http://www.importnew.com/author/wangxiaojie

http://www.importnew.com/1993.html

http://www.importnew.com/2057.html

http://www.importnew.com/3146.html

http://www.importnew.com/3151.html

相關文章
相關標籤/搜索