有時候碰到性能問題,好比一個java application出現out of memory,出現內存泄漏的狀況,再去修改bug可能會變得異常複雜,利用工具去分析整個java application 內存佔用狀況,而後再去走查代碼。java
首先先看一下,java內存分配的基本模型,因爲JVM內存劃分比較複雜,這裏只是簡單的說一下java內存劃分app
java 堆(heap):dom
Java 堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的工具
惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。性能
Java 堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC 堆」spa
因此Java 堆中還能夠細分爲:新生代和老年代;線程
新生代:全部新生成的對象首先都是放在年輕代的。年輕代的目標就是儘量快速的收集掉那些生命週期短的對象。orm
老年代(old Generation):在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。所以,能夠認爲年老代中存放的都是一些生命週期較長的對象對象
持久代(Prem Generation):用於存放靜態文件,現在Java類、方法等。持久代對垃圾回收沒有顯著影響,可是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候須要設置一個比較大的持久代空間來存放這些運行過程當中新增的類。持久代大小經過-XX:MaxPermSize=<N>進行設置。blog
再細緻一點的有Eden 空間、From Survivor 空間、To Survivor 空間等。若是從內存分配
的角度看,線程共享的Java 堆中可能劃分出多個線程私有的分配緩衝區(Thread Local
Allocation Buffer,TLAB)。不過,不管如何劃分,都與存放內容無關,不管哪一個區域,
存儲的都仍然是對象實例,進一步劃分的目的是爲了更好地回收內存,或者更快地分配內存。
java 棧區:
JVM中運行的每一個線程都擁有本身的線程棧,線程棧包含了當前線程執行的方法調用相關信息,咱們也把它稱做調用棧。隨着代碼的不斷執行,調用棧會不斷變化。
通常認爲 方法,局部變量,對象的引用 都是存在棧區內的。
java方法區:
方法區(Method Area)與Java 堆同樣,是各個線程共享的內存區域,它用於存
儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
不少人稱之爲永生代(Permanent Generation);
出現out of memory 接着會出現 java heap space,這種狀況是堆空間滿了,
可能出現的狀況,好比一個方法 進棧了,裏面有一段邏輯,最多見的就是打個比方
public void method(){
List<Model> list=...;
.... 執行代碼的邏輯
}
這個list 裏面有N多對象,可是執行下面的邏輯時內存溢出,這是由於方法在沒有出棧以前,java heap中的對象太多(方法在出棧以後GC 對 沒有引用的 對象進行垃圾回收)。
還有 就是有一些全局的變量或者集合 ,或者存在方法區的 內存 沒有進行及時清理,致使內存沒有釋放,這種稱之爲真泄漏。
2.1 利用jmap 進行內存分析,
jmap -heap pid //打印heap空間的概要,這裏能夠粗略的檢驗heap空間的使用狀況。
Heap Configuration:指java應用啓動時設置的JVM參數。像最大使用內存大小,年老代,年青代,持久代大小等。
Heap Usage:當時的heap實際使用狀況。包括新生代、老生代和持久代。
其中新生代包括:Eden區的大小、已使用大小、空閒大小及使用率。Survive區的From和To一樣。
有這個能夠很簡單的查看本進程的內存使用狀況。
能夠用於分析堆內存分區大小是否合理,新生代和老生代的大小分配是否合適等。
也許進程佔用的總內存比較多,但咱們在這裏能夠看到真正用到的並無多少,不少都是"Free"。內存使用的堆積大多在老年代,內存池露始於此,因此要格外關心「Old Generation」。
jmap -histo PID //這裏會生成一個類的統計報表,此表很是簡單,如顯示什麼類有多少個實例,共佔了多少字節等。
2.2 利用jmap 生成dump 文件,而後再利用mat工具進行分析。
jmap -dump:live,format=b,file=‘文件名 ’2657 //2657 是進程的PID 號
生成轉儲文件以後,file -open(該轉儲文件)
上面的餅狀圖顯示了內存分配的比例,經過dominator_tree能夠看到詳細信息。