JMX,Jstatd做好JVM應用上線的最後一層保障

目錄

一個成功的java項目標準並不僅僅是業務功能實現,但是縱觀國內,很多項目組在前期項目開發設計中只考慮了業務功能,沒有考慮項目後期維護的監控設計。沒有完善的監控運維設計,項目存活的壽命應該也不長吧?好的項目能夠吸引人留下來,並不斷強化項目的功能優化每一處代碼,壞的項目只會逼死人,不斷的增加齷齪代碼以至於根本無法維護。
當然從公司來說,業務的首要實現是公司能夠賺錢的有效保障,公司賺不了錢了,寫的在好的代碼也只能靜靜的躺在硬盤中。我想一個負責的開發人員不僅要能重視業務功能的實現,還能保證在項目上線運維中針對突發情況做到監控。

我理解的監控

我理解的監控分兩種,一種是運維的監控-監控整個集羣的各項資源的使用情況以及各個服務的存活情況,另一種是開發的監控-監控代碼問題導致的線程死鎖,OOM等,以及業務消息的歷史可回溯。
我是一名開放,這裏主要講講我的心得,開發中的監控。如何減少開發人員不必要的加班。

代碼異常監控

應用代碼在面對線上各種請求時,經常會發生死鎖,OOM等問題。這個時候我們如何去查看呢?
如果我們不想連上遠程服務器,通過本地的一些可視化工具連接遠程程序,查看遠程程序的線程,CPU,GC,堆內存等使用情況。

遠程主機配置jmx

這裏只是演示JMX的監控功能,JMX還有動態修改bean屬性等功能不在這一篇文章講解。

修改密碼,找到配置文件$JAVA_HOME/jre/lib/management/jmxremote.password.template,複製一份並改名爲jmxremote.password,然後修改只讀權限並編輯jmxremote.passwrod,取消以下兩行註釋:

#monitorRole QED
#controlRole R&D

修改要啓動的java程序啓動參數(JVM_OPTS)。

打開tomcat的bin目錄下的catalina.sh,加入以下內容**(非tomcat程序也類似)**
JAVA_OPTS="$JAVA_OPTS -Djava.rmi.server.hostname=192.168.19.131 
-Dcom.sun.management.jmxremote.port=18999       
-Dcom.sun.management.jmxremote.ssl=false    
-Dcom.sun.management.jmxremote.authenticate=false"

參數authenticate表示是否需要密碼認證,賦值爲true就會使用jmxremote.password設置的密碼。

修改文件權限

監控的程序是由哪個用戶啓動,則把jmxremote.password文件的權限改爲這個用戶的只讀權限,否則啓動程序會報錯:Error: Password file read access must be restricted。這些在jmxremote.password裏的註釋都有說明。比如,如果你是用intsmaze用戶啓動java程序
chown intsmaze jmxremote.password
chmod 400 jmxremote.password

啓動jvisualvm

先啓動待監控的程序

sh startup.sh

左邊欄,右鍵“遠程”>>“添加遠程主機”

左側欄,右鍵剛纔添加的遠程主機>>“添加jmx鏈接”,使用配置的端口


如果我們不配置JVM_OPTS參數,那麼我們在本地使用javaVisualVM是無法訪問遠程服務器上的tomcat服務的狀況,要想知道遠程服務器的狀況就必須使用CRT等工具連上服務器使用linux命令去查看程序的運行情況。

監控服務器上的java程序

在java -cp 命令中加入如下參數即可

java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=22222 -cp jmx.jar cn.intsmaze.thread.TestDeadThread

TestDeadThread類如下

public class TestDeadThread implements Runnable {
    int a, b;

    public TestDeadThread(int a, int b) {
        this.a = a;
        this.b = b;
    }

    public void run() {
        synchronized (Integer.valueOf(a)) {
            synchronized (Integer.valueOf(b)) {
                System.out.println(a + b);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
        for (int i = 0; i < 100; i++) {
            new Thread(new TestDeadThread(1, 2)).start();
            new Thread(new TestDeadThread(2, 1)).start();
        }
    }
}
JvisiualVM通過JMX的方式連接到遠程服務器上的JVM,此時能獲取到JVM的基本信息(啓動參數、系統屬性)、CPU使用情況、堆內存整體情況以及線程的整體情況等。但如果想通過Visual GC插件進一步瞭解堆內各區的情況的話,就會發現插件此時並不工作。


Visual GC插件不工作,是因爲此插件使用的協議是RMI,因此需要使用下面的jstatd方式進行連接。

jstatd 連接到遠程JVM

JVM jstat Daemon:守護進程,一個RMI服務器程序,用於監控本地所有JVM從創建開始直到銷燬整個過程中的資源使用情況,同時提供接口給監控工具(如這裏的VisualVM),讓工具能連接到本機所有的JVM。

啓動jstatd服務

${java_home}/bin目錄下啓動jstatd服務
[intsmaze@centos-Reall-131 bin]./jstatd
Could not create remote object
access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.rmi.server.ignoreSubClasses" "write")
        at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
        at java.security.AccessController.checkPermission(AccessController.java:884)
        at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
        at java.lang.System.setProperty(System.java:792)
        at sun.tools.jstatd.Jstatd.main(Jstatd.java:139)
由於jstatd server沒有提供任何對遠程client端的認證,客戶端程序獲取到本地當前用戶的所有JVM信息後可能存在安全隱患,所以jstatd要求啓動之前必須指定本地安全策略,否則jstatd進程無法啓動,拋出上面錯誤。

創建安全策略文件

在需要被監控的遠程主機創建一個安全策略文件,比如保存爲/home/intsmaze/jdk1.8.0_144/bin/jstatd-all.policy,內容如下:
grant codebase "file:/home/intsmaze/jdk1.8.0_144/lib/tools.jar" {
permission java.security.AllPermission;
};

啓動jjstatd帶參數

通過如下命令可以成功啓動jstatd server

./jstatd -J-Djava.security.policy=/home/intsmaze/jdk1.8.0_144/bin/jstatd-all.policy -J-Djava.rmi.server.logCalls=true
./jstatd -J-Djava.security.policy=/home/intsmaze/jdk1.8.0_144/bin/jstatd-all.policy &
向通過jstatd命令啓動的JVM(Main class:sun.tools.jstatd.Jstatd)傳遞參數,比如-J-Xms48m指定了Jstatd這個JVM的初始堆內存爲48MB

右鍵選擇建立jstatd連接

對應的遠程主機節點下會自動列出所有運行的JVM

JMX連接與JStatD連接的區別

JMX:使用JMX需要遠程JVM在啓動的時候開啓遠程訪問支持,設定JMX端口等,每一個JMX連接一個遠程JVM。
JStatD:使用jstatd連接方式時,需要在遠程主機上創建安全策略文件然後啓動jstatd進程,並且此進程需要一直保持運行狀態,客戶端可以看到遠程主機上當前用戶的所有JVM的信息,即只要創建一個jstatd連接。

linux命令監控jvm程序

如果我們不配置JMX和jstatd,那麼我們無法使用jvisiualVM去監控遠程JVM程序,要知道程序的運行狀態我們必須連上服務器去查看。

top命令查看各進程CPU佔用率

[intsmaze@centos-Reall-131 ~]$ top
top - 13:04:07 up 3 min,  2 users,  load average: 0.00, 0.01, 0.00
Tasks: 104 total,   1 running, 103 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.2%sy,  0.0%ni, 99.8%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   2086348k total,   224720k used,  1861628k free,    37484k buffers
Swap:  2064376k total,        0k used,  2064376k free,    91204k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                       
  385 root      20   0     0    0    0 S  0.3  0.0   0:00.02 flush-8:0                                                                                                     
 2211 intsmaze  20   0  858m  25m 9448 S  0.3  1.2   0:00.87 java                                                                                                          
  1. 第一行:load average: 0.41, 0.45, 0.43 系統負載,即任務隊列的平均長度。1分鐘前、5分鐘前、15分鐘前平均負載

  2. 第二行:Tasks: 141 total 進程總數,0 zombie 殭屍進程數

  3. 第三行爲cpu信息
    6.1% us 用戶空間佔用CPU百分比
    1.5% sy 內核空間佔用CPU百分比
    0.0% ni 用戶進程空間內改變過優先級的進程佔用CPU百分比
    92.2% id 空閒CPU百分比
    0.0% wa 等待輸入輸出的CPU時間百分比
    0.0% hi 硬件中斷
    0.0% si 軟件中斷
    0.0%st 實時

  4. 第四、五行爲內存信息。
    Mem: 191272k total 物理內存總量
    22052k buffers 用作內核緩存的內存量
    Swap: 192772k total 交換區總量
    123988k cached 緩衝的交換區總量

進程中每個線程佔用cpu情況

[intsmaze@centos-Reall-131 ~]$ top -Hp 2461
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                                                                                                                                                                                                            
 2462 intsmaze  20   0  870m  25m 9416 S  30.0  1.2   0:00.28 java                                                                                                          
 2463 intsmaze  20   0  870m  25m 9416 S  0.0  1.2   0:00.00 java                                                                                                          
 2464 intsmaze  20   0  870m  25m 9416 S  0.0  1.2   0:00.00 java

定位線程的運行情況

Jstack是JDK自帶的命令行工具,主要用於線程Dump分析,能得到運行java程序的java stack和native stack的信息,可以輕鬆得知當前線程的運行情況。

jstack -l [pid]查看所有線程信息

jstack -l 2238 > intsmaze.log
[intsmaze@centos-Reall-131 ~]$ jstack -l 2461
"Thread-200":
        at cn.intsmaze.thread.TestDeadThread.run(TestDeadThread.java:29)
        - waiting to lock <0x9d62a3a0> (a java.lang.Integer)
        at java.lang.Thread.run(Thread.java:748)
"Thread-10":
        at cn.intsmaze.thread.TestDeadThread.run(TestDeadThread.java:30)
        - waiting to lock <0x9d62a390> (a java.lang.Integer)
        - locked <0x9d62a3a0> (a java.lang.Integer)
        at java.lang.Thread.run(Thread.java:748)

jstack命令生成的thread dump信息包含了JVM中所有存活的線程,爲了分析指定線程,必須找出對應線程的調用棧,應該如何找?

jstack -l [pid] | grep 16進制

top -Hp [pid] 中獲取到了佔用cpu資源較高的線程pid,將該pid轉成16進制的值,在thread dump中每個線程都有一個nid,找到對應的nid即可。

得到2462 的十六進制值
···
[intsmaze@centos-Reall-131 ~]$ printf "%x\n" 2462 
99e
···
jstack -l 21711 | grep 99e
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x99e in Object.wait()

在nid=0x99e 的線程調用棧中,CPU消耗在PollIntervalRetrySchedulerThread這個類的Object.wait(),然後去觀察自己寫的業務代碼。

源碼插樁

當初小弟運氣好,做了一個比較核心的紅包業務,基本上每週都會有新的版本發佈。而且面對的人羣是普通用戶,用戶一發現消費沒有中紅包,就會打客服,然後我這邊就會收到反饋,這個時候就要根據客戶的交易id查詢原因給出反饋。如果當初在開發的時候,沒有考慮到源碼插樁,那麼這個時候我就會頭疼,推出去的報文相應字段確實沒有中紅包,然後我去看規則是否是這筆交易沒有滿足,然後找了幾天還是沒有給出讓人信服的答案。在這個系統架構師對我們所有的系統做了源碼插樁,一條記錄從進入系統,走過那些條件判斷的流程,每一個條件判斷的值都進行了插樁,然後匯聚成一條消息處理記錄存儲在hbase。然後面對這種情況,我們只需要去hbase中查詢一下,拿出這條消息在整個系統的路徑狀況變一目瞭然了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章