JVM-11虚拟机性能监控与故障处理工具之【JDK的可视化工具-JConsole】
文章目錄
- 思維導圖
- 概述
- JConsole: Java監視與管理平臺
- 啟動jconsole
- 內存監控示例
- VM ARGS
- 代碼
- JConsole監控展示及說明
- 擴展問題
- 沒有指定-Xmn,如何確定新生代和Eden的大小
- 為何老年代的柱狀圖信息仍顯示峰值狀態,如何調整代碼回收該區域
- 線程監控示例
- 活鎖等待示例
- 死鎖等待示例
思維導圖
概述
JVM-10虛擬機性能監控與故障處理工具之【命令行】我們接觸了JDK提供的命令行工具,JDK還為我們提供了兩個功能強大的可視化工具:JConsole和VisualVM。
JConsole在JDK1.5版本供就已經提供,而VisualVM是在JDK1.6 Update7中才首次發布。現在已經成為Oracle主力推動的多合一故障處理工具。
JConsole: Java監視與管理平臺
JConsole ( Java Monitoring and Management Console)是一種基于JMX的可視化監控、管理工具。 它管理部分的功能是針對JMX MBean進行管理。 由于MBean可以在代碼、中間件服務器的管理控制臺或者所有符合JMX規范的軟件進行訪問。
所以我們這里重點介紹JConsole監視部分的功能。
啟動jconsole
通過JDK安裝目錄下的bin目錄“jconsole.exe”啟動JConsole后,會自動列出本機運行的所有虛擬機進程,無需通過jps來查詢了。除了本地進程,也可以使用“遠程進程”的共鞥來連接遠程服務器。
內存監控示例
VM ARGS
-Xms100m -Xmx100m -XX:+UseSerialGC -verbose:gc -XX:+PrintGCDetails堆最小內存100M 最大內存100M, 即為不可擴展。 使用Serial垃圾收集器。同時為了方便和圖形化界面展示的數據比對,我們打印GC日志以及堆信息
代碼
package com.artisan.gc;import java.util.ArrayList; import java.util.List;public class OOMObject {// 內存占位符對象 一個object 大約占用64kbpublic byte[] object = new byte[64 * 1024];public static void fillData2Heap(int number) throws InterruptedException {List<OOMObject> objectList = new ArrayList<OOMObject>();for (int i = 0; i < number; i++) {// 休眠50毫秒,使曲線變化的更加明顯Thread.sleep(50);objectList.add(new OOMObject());}System.gc();}public static void main(String[] args) throws InterruptedException {fillData2Heap(1000);// 為了讓JConsole上的內存變化體現在工具上,讓主線程不要立刻退出,休眠10SThread.sleep(10 * 1000);}}JConsole監控展示及說明
運行程序,執行JConsole.exe .
上圖為整個堆內存的使用情況,可以看到隨著程序的運行,整個堆中內存使用是一個平滑向上增長的曲線(平的部分為Thread.sleep(10 * 1000)造成的,)程序運行結束退出后,內存歸為0。
然后我們我們來看下Eden區域的曲線變化情況,如下圖。
可以很明顯的看到發生了2次 Minor GC. 和輸出的GC日志信息一致。
同時在這里也可以看出來
新生代主要是復制算法,這里的統計信息也是 2次收集。
擴展問題
沒有指定-Xmn,如何確定新生代和Eden的大小
可以從如下圖中查看每個分區的內存分配情況
當然也可以粗略計算出來
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。
堆內存為100M,采用默認的–XX:NewRatio,可以推算出 新生代占用1/3 , 33M .
沒有指定-XX:SurvivorRatio , 默認為8。
新生代又分為 Eden區、SurvivorFrom、SurvivorTo三個區
默認的,Edem : from : to = 8 : 1 : 1 ( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什么時候,總是有一塊 Survivor 區域是空閑著的。
因此,新生代實際可用的內存空間為 9/10 ( 即90% )的新生代空間,只有10%的內存被“浪費”,最大限度的節約資源。
Eden內存區的大小為 (33M * 9/10 ) 新生代可用的空間 * 8/9 (Eden占用新生代的比例) = 27033.6KB 。 和 圖中顯示的27328 KB 接近。
為何老年代的柱狀圖信息仍顯示峰值狀態,如何調整代碼回收該區域
可以看到老年代仍然占據很大的內存空間,是因為 雖然執行了System.gc()。 但是是在fillData2Heap方法內執行的。 在方法內,List<OOMObject>是存活的,該方法還沒退出,gc無法回收。
只要在該方法外執行GC就可以將老年代的內存回收。 我們來實踐下
啟動JConsole
線程監控示例
上面的“內存”可以看做是 jstat命令的話,“線程”頁簽就可相當于jstack命令, 遇到線程停頓時可以使用這個頁簽進行監控分析。 用于定位線程長時間停頓的原因,比如等待外部資源(數據庫連接、網絡資源、設備資源等),鎖等待(活鎖和死鎖)、死循環。
活鎖等待示例
package com.artisan.gc;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;public class ThreadWaitTest {/*** * * @Title: createBusyThread* * @Description: 線程死循環演示* * * @return: void*/public static void createBusyThread() {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {// do something// System.out.println("busyThread running");}}}, "busyThread");// 啟動線程thread.start();}/*** * * @Title: createLockThread* * @Description: 線程鎖等待演示* * @param lock* * @return: void*/public static void createLockThread(final Object lock) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}, "lockThread");thread.start();}public static void main(String[] args) throws IOException {System.out.println("請輸入");BufferedReader bReader = new BufferedReader(new InputStreamReader(System.in));bReader.readLine();createBusyThread();bReader.readLine();Object object = new Object();createLockThread(object);}}運行程序,啟動 jconsole, 選擇該進程 ,選擇 “線程”頁簽,
首先查看main線程
堆棧追蹤顯示
BufferedReader在readBytes方法中等待System.in的鍵盤輸入,這時線程狀態為Runnable狀態,Runnable狀態的線程會被分配運行時間,但readBytes方法檢測到流沒有更新會立刻歸還執行令牌,這種等待只消耗很小的cpu資源。
輸入內容后,接著監控 “busyThread”線程
可以看出一直在ThreadWaitTest.java的 第24行停留 ,我們查看ThreadWaitTest的24行內容
這時線程為Runnable狀態,而且沒有歸還線程執行令牌的動作,會在空循環上用盡全部的執行時間,直到線程切換,這種等待會消耗較多的CPU資源。
接著控制臺再輸入內容
繼續回到jconsole查看 “lockThread”線程
lockThread線程正在處于正常的活鎖等待,只要lock對象的notify或者notifyAll方法被調用,這個線程便可以繼續執行。
死鎖等待示例
package com.artisan.gc;public class ThreadLockTest {static class ThreadDemo implements Runnable {int a, b;public ThreadDemo(int a, int b) {this.a = a;this.b = b;}@Overridepublic 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 < 200; i++) {new Thread(new ThreadDemo(1, 2)).start();new Thread(new ThreadDemo(2, 1)).start();}} }先分析下造成死鎖的原因: Integer.valueOf()方法基于減少對象創建次數和節省內存的考慮,【-128,127】之間的數字會被緩存,當valueOf()方法傳入參數在這個范圍內的時候,將直接返回緩存中的對象。 也就是說上述代碼中雖然調用了200次的Integer.valueOf()方法,但是入參為1和2,一共就返回了2個不同的對象。 假如在某個線程的兩個synchronized塊之間發生了一次線程切換,那么就會出現線程A等著被線程B持有的Integer.valueOf(1),線程B等著被線程A持有的Integer.valueOf(2),造成死鎖。
點擊“檢測到死鎖”按鈕,如果 有死鎖將會出現一個新的“死鎖”頁簽。
可以看到Thread-27和Thread-28互相阻塞,已經沒有等到鎖釋放的希望了。
總結
以上是生活随笔為你收集整理的JVM-11虚拟机性能监控与故障处理工具之【JDK的可视化工具-JConsole】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JVM-10虚拟机性能监控与故障处理工具
- 下一篇: JVM-12虚拟机性能监控与故障处理工具