JVM分析工具備不少;java
jdk自帶工具:jconsole、jvisualvm數據庫
其餘工具:jprofile ,yourkit等緩存
不要在線上用,影響性能,在測試環境中使用。tomcat
1、jconsole ——jdk自帶工具分析網絡
內存監控——內存」頁籤至關於可視化的jstat命令的話jvm
一、沒有任何操做,內存還在上升,why???jsp
rmi協議致使的,rmi通訊的時候會產生一些對象,因此這裏內存會上升,能夠點擊右上角的綠色那個,斷開rmi鏈接,就不會增加了。ide
執行gc這裏是full-gc,系統調用都是full-gc。工具
二、頗有規律的波動,是在進行y-gc。性能
相關gc日誌以下:
[GC (Allocation Failure) [DefNew: 27277K->3392K(30720K), 0.0349173 secs] 27277K->14749K(99008K), 0.0350411 secs] [Times: user=0.03 sys=0.00, real=0.04 secs] [GC (Allocation Failure) [DefNew: 30691K->3378K(30720K), 0.0446635 secs] 42049K->39217K(99008K), 0.0447387 secs] [Times: user=0.03 sys=0.01, real=0.04 secs] [GC (Allocation Failure) [DefNew: 30679K->3372K(30720K), 0.0408609 secs] 66518K->64734K(99008K), 0.0409604 secs] [Times: user=0.02 sys=0.02, real=0.04 secs] [Full GC (System.gc()) [Tenured: 61362K->66352K(68288K), 0.0372192 secs] 67024K->66352K(99008K), [Metaspace: 9535K->9535K(1058816K)], 0.0373411 secs] [Times: user=0.05 sys=0.00, real=0.04 secs]
三、出現問題
下面代碼的做用是以64kb/50ms的速度王java堆中填充數據,一共1000次,而後使用jconsole監控,
/** * 內存佔位符對象,一個OOMObject大約佔64KB */ static class OOMObject { public byte[] placeholder = new byte[64 * 1024]; }  public static void fillHeap(int num) throws InterruptedException { List<OOMObject> list = new ArrayList<OOMObject>(); for (int i = 0; i < num; i++) { //稍做延時,令監視曲線的變化更加明顯 Thread.sleep(50); list.add(new OOMObject()); } System.gc(); }  public static void main(String[] args) throws Exception { fillHeap(1000); }
程序運行後,在「內存」頁籤中能夠看到內存池Eden區的運行趨勢呈現折線狀,如圖4-6所示。而監視範圍擴大至整個堆後,會發現曲線是一條向上增加的平滑曲線。而且從柱狀圖能夠看出,在1000次循環執行結束,運行了System.gc()後,雖然整個新生代Eden和Survivor區都基本被清空了,可是表明老年代的柱狀圖仍然保持峯值狀態,說明被填充進堆中的數據在System.gc()方法執行以後仍然存活。
爲什麼執行了System.gc()以後,圖4-6中表明老年代的柱狀圖仍然顯示峯值狀態,代碼須要如何調整才能讓System.gc()回收掉填充到堆中的對象?
答:執行完System.gc()以後,空間未能回收是由於List<OOMObject>list對象仍然存活,fillHeap()方法仍然沒有退出,所以list對象在System.gc()執行時仍然處於做用域以內。若是把System.gc()移動到fillHeap()方法外調用就能夠回收掉所有內存。
線程監控——「線程」頁籤的功能至關於可視化的jstack命令
遇到線程停頓時可使用這個頁籤進行監控分析。線程長時間停頓的主要緣由主要有:等待外部資源
(數據庫鏈接、網絡資源、設備資
源等)、死循環
、鎖等待
(活鎖和死鎖)
/**
* 等待控制檯輸入
* @throws IOException
*/
public static void waitRerouceConnection () throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.readLine();
}
/**
/** * 線程死循環演示 */ public static void createBusyThread() { Thread thread = new Thread(new Runnable() { @Override public void run() { while (true) //第41行 ; } }, "testBusyThread"); thread.start(); }  /** * 線程鎖等待演示 */ public static void createLockThread(final Object lock) { Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "testLockThread"); thread.start(); }  public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); br.readLine(); createBusyThread(); br.readLine(); }
程序運行後,首先在「線程」頁籤中選擇main線程,如圖4-7所示。堆棧追蹤顯示BufferedReader在readBytes方法中等待System.in的鍵盤輸入,這時線程爲Runnable狀態,Runnable狀態的線程會被分配運行時間,但readBytes方法檢查到流沒有更新時會馬上歸還執行令牌,這種等待只消耗很小的CPU資源。
接着監控testBusyThread線程,testBusyThread線程一直在執行空循環,從堆棧追蹤中看到一直在MonitoringTest.java代碼的41行停留,41行爲:while (true)。這時候線程爲Runnable狀態,並且沒有歸還線程執行令牌的動做,會在空循環上用盡所有執行時間直到線程切換,這種等待會消耗較多的CPU資源。以下圖:
testLockThread線程在等待着lock對象的notify或notifyAll方法的出現,線程這時候處於WAITING狀態,在被喚醒前不會被分配執行時間。以下圖:
線程死鎖案例演示:
testLockThread線程正在處於正常的活鎖等待,只要lock對象的notify()或notifyAll()方法被調用,這個線程便能激活以繼續執行。下面代碼演示了一個沒法再被激活的死鎖等待。
/** * 線程死鎖等待演示 */ static class SynAddRunalbe implements Runnable { int a, b; public SynAddRunalbe(int a, int b) { this.a = a; this.b = b; }  @Override public void run() { synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } } }  public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(new SynAddRunalbe(1, 2)).start(); new Thread(new SynAddRunalbe(2, 1)).start(); } }
這段代碼開了200個線程去分別計算1+2以及2+1的值,其實for循環是可省略的,兩個線程也可能會致使死鎖,不過那樣機率過小,須要嘗試運行不少次才能看到效果。通常的話,帶for循環的版本最多運行2~3次就會遇到線程死鎖,程序沒法結束。形成死鎖的緣由是Integer.valueOf()方法基於減小對象建立次數和節省內存的考慮,[-128,127]之間的數字會被緩存,當valueOf()方法傳入參數在這個範圍以內,將直接返回緩存中的對象。也就是說,代碼中調用了200次Integer.valueOf()方法一共就只返回了兩個不一樣的對象。假如在某個線程的兩個synchronized塊之間發生了一次線程切換,那就會出現線程A等着被線程B持有的Integer.valueOf(1),線程B又等着被線程A持有的Integer.valueOf(2),結果出現你們都跑不下去的情景。
出現線程死鎖以後,點擊JConsole線程面板的「檢測到死鎖」按鈕,將出現一個新的「死鎖」頁籤,以下圖:
上圖很清晰地顯示了線程Thread-43在等待一個被線程Thread-12持有Integer對象,而點擊線程Thread-12則顯示它也在等待一個Integer對象,被線程Thread-43持有,這樣兩個線程就互相卡住,都不存在等到鎖釋放的但願了。
2、jvisualvm ——jdk自帶分析工具, jconsole的升級版
JVisualVM的有一個很大的優勢:不須要被監視的程序基於特殊Agent運行,所以它對應用程序的實際性能的影響很小,使得它能夠直接應用在生產環境中。這個優勢是JProfiler、YourKit等工具沒法與之媲美的。
jvisualvm須要本身安裝插件,點擊工具-插件,而後點擊可用插件,都安裝上。
執行垃圾回收:system.gc
堆 Dump:把內存信息dump下來
還能夠對dump下的東西進行分析,裝入
對tomcat下的test1裏的init2.jsp進行壓測, http://192.168.1.17:8080/test1/init2.jsp,而後監控線程,cpu,內存等。
一、監控線程
二、cpu抽樣
cpu樣例是 方法維度的
線程cpu時間是 線程維度的
線程cpu看8080.exec線程的就好了,這纔是真正工做的線程。
一段時間後,而後進行快照,快照保存後進行分析,看組合視圖,而且按總時間(cpu)進行排序。
是init2.jsp的致使的cpu太高,好的時候,能夠快照到init2.jsp下的add list方法的問題。
三、內存抽樣
能夠看到TestBean這個類佔用的內存比較多。
java mem = max mem+direct mem + meta mem
遇到問題:java程序,有次壓測,系統響應很慢,可是top查看 cpu、負載都很正常,排除cpu問題
而後看jvm內存問題,jstat 看堆的狀況,也沒有頻繁的full-gc,奇怪了,什麼問題呢?
cpu、內存都沒問題,難道是磁盤致使的??
看磁盤隊列,使用比率,pidstat等看io的命令,也很正常。
有點問題,壓着壓着,系統響應很慢,問題仍是在操做系統層級。看操做系統內存資源,
free內存在減少,swap交換空間內存開始上升(堆內存不會大於java內存和堆外內存的)
這裏是一個16G的機器,堆內存配了8個G,持久帶配了0.5G,總共才8.5個G,怎麼會內存佔沒了呢??
看系統進程誰消耗最多內存呢? top 按內存排序,或者pidstat
仍是java佔最多,其餘沒有佔不少,看RES組成超過了8.5G,這裏還有個direct mem,這裏不設置,會和堆內存同樣大都爲8.5個G,也會慢慢增大。
這裏要麼別用,要麼指定大小,也是JVM裏配置。
3、jpfiler
添加一個遠程鏈接,如何配置見之前的博客。
到監控頁面後,選擇live memory,而後標記Mark (至關於增量),而後訪問幾回下面的init2.jsp頁面,能夠看到以下圖褐色的增量。
這裏內存一直增長而且通過gc以後,沒有減小,分析這可能就是這個對象形成內存溢出的緣由。
如何分析???
這裏選中這個對象放到heap worker裏面進行分析。
heap worker頁面,references 視圖看引用關係,biggest objects看大對象,通常就看這兩個視圖,優先看大對象。
分析cpu,基本就堆和cpu(棧)就行了。
問題:壓測的時候要把cpu打滿,和cpu過高要進行分析,這是矛盾的?
若是一個線程消耗不少則說明這個線程是有問題的,若是每一個線程的使用cpu都差很少的狀況,則證實cpu使用的是正常的。