JVM之工具分析

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[641024]; }  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使用的是正常的。

相關文章
相關標籤/搜索