服務端性能問題排查及優化---CPU高問題分析

CPU高是常見的性能問題,但CPU高並不一定都是有問題,有可能你的業務就是CPU密集型的業務。
如果CPU資源有限的情況下,本文可以提供一下優化CPU使用的方法,可以儘可能的優化,使CPU在可能的範圍內降到最低。

可能造成CPU高的情況 :

  • 代碼Bug
  • 意外的死循環
  • 線程數太多,頻繁線程切換
  • 頻繁的FGC
  • 不正常的使用某些類

可能導致CPU高的情況

  1. 代碼Bug
    可能性太多…
  2. 意外的死循環
    手抖寫的死循環或者計算失誤導致死循環
  3. 線程數太多(線程數量是否比預期的異常的高)
    大量的線程切換:線程數太多可能會導致頻繁的線程上下文的切換,浪費CPU資源。
    頻繁創建銷燬線程:線程的創建和銷燬對系統的資源消耗比較大,如果一直在頻繁的創建銷燬臨時線程導致資源佔用,可能需要考慮下是否有其他更好的方案了。
    線程池不正常的使用:比如Cache線程池初始化比較少、最小和最大的數量相差比較大、業務併發突然增大可能導致同時去創建線程。
  4. 頻繁的FGC
    導致頻繁的FGC也會導致CPU高,比如由於JVM參數設置的不合理,年輕代和老年代的比例比例不合理導致頻繁的FGC等。
  5. 不正常的使用某些類
    比如Map的初始大小給的太小,而後續的使用中存的東西太多,可能會頻繁的resize。
    比如大數據量List的頻繁查找,clone等。
    比如可重複利用資源的頻繁初始化操作。

分析過程

用到的工具和命令

jvisualvm  
jstack,jstat,jmap,ps,top

CPU高的集中情況

  • 情況一:單核CPU使用率一直100%
    大多情況爲死循環或者某個線程一直在執行大量運算操作

  • 情況二:單核CPU使用率不定時100%
    應用本身有週期性的任務
    非人爲的週期性操作,比如FGC,大量數據的copy(list,map)

  • 情況三:每核CPU使用率都高
    需要結合堆棧、代碼、資源佔用、內存情況等,全面的考慮和分析。

分析方法

  • 方法1
    Java Visualvm直接連接進程分析,能夠比較直觀的看到每個方法所用的CPU時間,更容易定位問題。大多數情況下生產環境無法使用。適用於被測對象問題能夠復現,且配置的遠程rmi監控的情況。生產環境不會配置遠程監控。

    java服務啓動時需配置啓動參數:
    -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.143.136

    使用方法見以下演示。

  • 方法2
    在CPU高的時候抓線程堆棧,適用於CPU相對比較高的情況,如果條件允許可以把服務壓力加大到儘量大,使CPU儘量高後去分析問題。

    抓取線程執行堆棧
    jstack pid > /tmp/jstack.txt

    找到佔用cpu時間比較多的線程id
    ps -mp pid -o THREAD,tid,time | awk '{printf $2" "$8"\n" }' | sort

    把線程id轉換爲10進制,8進制?
    printf "%x\n" tid

    輸出線程堆棧
    jstack pid | grep –A 20 tid

    連續抓取幾次堆棧信息,分析正在運行的線程,分析當前操作是否耗費CPU,這麼多運行的是否正常,業務堆棧是否正常,該業務可能出現的問題等等。

案例分析

案例介紹

測試時發現使用的的測試客戶端CPU高,表現爲運行一段時間後,CPU突然100%,出現該情況後不能恢復正常。

分析過程

  1. 首先抓取客戶端的線程堆棧,看看能否發現什麼可以的地方。
  2. 分析堆棧發現線程有1000多,大部分爲BLOCKED狀態,ACTIVE狀態基本看到的都是nio的,暫時沒看到問題。
  3. 繼續搜索本地package的以下名稱,看看有沒有在執行自己代碼的地方,正好發現一些類似以下的信息。
Thread 1134: (state = IN_NATIVE)
 - java.net.NetworkInterface.getAll() @bci=0 (Compiled frame; information may be imprecise)
 - java.net.NetworkInterface.getNetworkInterfaces() @bci=0, line=334 (Compiled frame)
 - com.alibaba.rocketmq.remoting.common.RemotingUtil.getLocalAddress() @bci=0, line=112 (Compiled frame)
 - com.alibaba.rocketmq.client.ClientConfig.<init>() @bci=19, line=32 (Compiled frame)
 - com.alibaba.rocketmq.client.producer.DefaultMQProducer.<init>(java.lang.String, com.alibaba.rocketmq.remoting.RPCHook) @bci=1, line=95 (Compiled frame)
 - com.alibaba.rocketmq.client.producer.DefaultMQProducer.<init>(java.lang.String) @bci=3, line=86 (Compiled frame)
 - ********************MQProducer.<init>(java.lang.String, java.lang.String) @bci=71, line=62 (Compiled frame)
 - ********************.RocketMQ.sendMessage() @bci=76,line=119 (Compiled frame)     // 119爲源代碼行號
 - ********************.RocketMQ$1$1.safeRun() @bci=7, line=53 (Compiled frame)
 - ********************.SafeRunnable.run() @bci=1, line=13 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=95, line=1145 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor$Worker.run() @bci=5, line=615 (Interpreted frame)
 - java.lang.Thread.run() @bci=11, line=745 (Interpreted frame)

發現這些線程都在執行java.net.NetworkInterface.getAll() ,此方法比較耗費CPU,之前遇到過類似案例。接着分析爲什麼這幾個線程會卡到這。

  1. 根據代碼分析都在執行這個操作的原因。
public void sendMessage() {
    try {
        // 略…
        Message msg = new Message("Performace", msgContent.getBytes("UTF-8"));
        if (producer == null) {
            producer = new MQProducer("Performace", "192.168.143.135:9876");      (1)
            producer.start();
            rst = producer.product(msg);
        } else {
            rst = producer.product(msg);
        }
        // 略…
    } catch (Exception e) {
        if (producer != null) {
            producer.shutdown();
            producer = null;
        }
    }
}

sendMessage方法會被隨機的註冊到一個timer線程池上,有可能會在同一時間點或者很近時間點同時執行該方法。
producer.product(msg);爲給遠端發送信息,如果因爲網絡原因或者其他未知原因導致Exception,會把producer賦值爲null,當再次執行sendMessage會重新初始化producer,如果恰好有多線程併發執行sendMessage,可能會導致重複初始化以及其他併發問題,導致惡性循環,恰好這個過程對CPU消耗比較多。

最後

以上是一個工作上簡單案例的分析過程,實際工作中遇到的問題可能會複雜的多,過程可能會更曲折,需要從更多的方面去了解被測對象,甚至需要比開發自己更瞭解整個系統的架構,才能從多個方面去考慮問題,查找問題的真正原因。
本文由郭軍英提供

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章