JVM內存溢出和死鎖監控與分析


1.通過jstat命令進行查看堆內存使用情況

先隨便啟動一個(java的應用程序就行)Tomcat服務,在命令行里輸入jps -l命令查看進程號

 

 

1.1 查看class加載統計

jstat -class 進程號

 

 

說明:

  • Loaded:加載class的數量
  • Bytes:所占用空間大小
  • Unloaded:未加載數量
  • Bytes:未加載占用空間
  • Time:時間

1.2查看編譯統計

jstat -compiler 進程號

 

 

 

說明:
  • Compiled:編譯數量。
  • Failed:失敗數量
  • Invalid:不可用數量
  • Time:時間
  • FailedType:失敗類型
  • FailedMethod:失敗的方法

1.3垃圾回收統計

jstat -gc 進程號 間隔時間(毫秒) 打印次數

統計一次

 

 

每過1000毫秒統計一次,一共統計三次

 

 

 說明:

 

  • S0C:第一個Survivor區的大小(KB)
  • S1C:第二個Survivor區的大小(KB)
  • S0U:第一個Survivor區的使用大小(KB)
  • S1U:第二個Survivor區的使用大小(KB)
  • EC:Eden區的大小(KB)
  • EU:Eden區的使用大小(KB)
  • OC:Old區大小(KB)
  • OU:Old使用大小(KB)
  • MC:方法區大小(KB)
  • MU:方法區使用大小(KB)
  • CCSC:壓縮類空間大小(KB)
  • CCSU:壓縮類空間使用大小(KB)
  • YGC:年輕代垃圾回收次數
  • YGCT:年輕代垃圾回收消耗時間
  • FGC:老年代垃圾回收次數
  • FGCT:老年代垃圾回收消耗時間
  • GCT:垃圾回收消耗總時間

2.使用jmap對內存進行分析

2.1查看堆內存情況

 

 

 2.2查看內存中對象數量及大小

查看所有對象,包括活躍以及非活躍的

jmap ‐histo <進程號> | more

只查看活躍的對象

jmap ‐histo:live <進程號> | more

對象說明

  • B byte
  • C char
  • D double
  • F float
  • I int
  • J long
  • Z boolean
  • [ 數組,如[I表示int[]
  • [L+類名 其他對象

2.3將內存使用情況dump到文件中

jmap ‐dump:format=b,file=dumpFileName <pid>

 

 

 

 

 

 2.4通過jhat對dump文件進行分析

一個未被占用的端口就行了

jhat ‐port <port> <file>

啟動成功如下圖

 

 

 使用瀏覽器訪問本機的9999(自己設置的)端口

網頁中顯示的就是所有的對象,直接拉到最下面可以使用OQL進行查詢

 

 

 OQL語法類似於SQL語句,如果不會使用可以查看幫助

 

 

 查詢字符串長度大於等於1000的對象

select s from java.lang.String s where s.value.length>=1000

 

 

 這樣對dump文件進行分析的結果還是有些不太清晰,接下來

3.使用MAT工具對dump文件進行分析

工具下載地址 下載和自己jdk對應位數的

下載完成,解壓並打開

 

 

 

 

 

 

 

 

 

 

 

 4.內存泄漏定位與分析

編寫導致內存泄漏的代碼,啟動時修改內存大小

復制代碼
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class TestJvmOutOfMemory {
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 10000000; i++) {
            String str = "";
            for (int j = 0; j < 1000; j++) {
                str += UUID.randomUUID().toString();
            }
            list.add(str);
        }
        System.out.println("ok");
    }
}

JVM參數如下

-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError

 

 

 

 執行一段時間程序停止運行,拋出異常,並在項目的根目錄下生成一個文件

 

 

 

 

 

 接下來使用MAT工具分析上面的文件

 

 

 由上圖可見,占用了總內存的90%導致了內存不足程序的退出,這種情況下不適合修改JVM參數,應當找到導致問題所在的代碼,進行調整

點擊上圖中的Details可以看見每個對象占用的多大內存

 

 

 5.使用jstack監控死鎖問題

jstack <pid>

先來了解一下線程的狀態

初始態(NEW)

  創建一個Thread對象,但還未調用start()啟動線程時,線程處於初始態。

運行態(RUNNABLE),在Java中,運行態包括 就緒態 和 運行態。

  就緒態

    該狀態下的線程已經獲得執行所需的所有資源,只要CPU分配執行權就能運 行。

    所有就緒態的線程存放在就緒隊列中。

  運行態

    獲得CPU執行權,正在執行的線程。

    由於一個CPU同一時刻只能執行一條線程,因此每個CPU每個時刻只有一條 運行態的線程。

阻塞態(BLOCKED)

  當一條正在執行的線程請求某一資源失敗時,就會進入阻塞態。

   而在Java中,阻塞態專指請求鎖失敗時進入的狀態。

   由一個阻塞隊列存放所有阻塞態的線程。

   處於阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執 行。

等待態(WAITING)

  當前線程中調用wait、join、park函數時,當前線程就會進入等待態。

  也有一個等待隊列存放所有等待態的線程。

  線程處於等待態表示它需要等待其他線程的指示才能繼續運行。

  進入等待態的線程會釋放CPU執行權,並釋放資源(如:鎖)

超時等待態(TIMED_WAITING)

  當運行中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就 會進入該狀態;

   它和等待態一樣,並不是因為請求不到資源,而是主動進入,並且進入后需要其 他線程喚醒;

   進入該狀態后釋放CPU執行權 和 占有的資源。

   與等待態的區別:到了超時時間后自動進入阻塞隊列,開始競爭鎖。

終止態(TERMINATED)

   線程執行結束后的狀態。

 

模擬死鎖的代碼

public class TestDeadLock {
    private static Object obj1 = new Object();
    private static Object obj2 = new Object();

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable {
        @Override
        public void run() {
            synchronized (obj1) {
                System.out.println("Thread1 拿到了 obj1 的鎖!");
                try {
                // 停頓2秒的意義在於,讓Thread2線程拿到obj2的鎖
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println("Thread1 拿到了 obj2 的鎖!");
                }
            }
        }
    }

    private static class Thread2 implements Runnable {
        @Override
        public void run() {
            synchronized (obj2) {
                System.out.println("Thread2 拿到了 obj2 的鎖!");
                try {
                // 停頓2秒的意義在於,讓Thread1線程拿到obj1的鎖
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println("Thread2 拿到了 obj1 的鎖!");
                }
            }
        }
    }

}

使用jstack加上進程號進行查看

 

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM