當前位置:首頁 > 公眾號精選 > 21ic電子網
[導讀]1、CPU 使用率怎么計算? CPU% = (1 - idleTime / sysTime) * 100 idleTime:CPU處于空閑狀態(tài)的時間 sysTime:CPU處于用戶態(tài)和內核臺的時間總和 2、CPU 使用率跟啥有關系? 常聽說計算密集型的程序是比較耗 CPU 使用率的。 3、CPU 與進程、線程有關系么? 現


1、CPU 使用率怎么計算?


CPU% = (1 - idleTime / sysTime) * 100

  • idleTime:CPU處于空閑狀態(tài)的時間

  • sysTime:CPU處于用戶態(tài)和內核臺的時間總和


2、CPU 使用率跟啥有關系?


常聽說計算密集型的程序是比較耗 CPU 使用率的。


3、CPU 與進程、線程有關系么?


現在分時操作系統(tǒng)是通過循輪方式分配時間片進行進程調度的,如果進程在等待或阻塞,不會造成 CPU 資源使用。線程稱為輕進程,共享進程資源,關于線程的調度,CPU 對于線程也是分時調度。而在 Java 中,線程的調用由 JVM 負責,線程的調度一般有兩種模式,分時調度和搶占式調度。



4、一個 while 死循環(huán),會不會引起 CPU 使用率飚升?


會的。


先不說別的,死循環(huán)會調用 CPU 寄存器進行計數,這個操作就會占用 CPU。其次,如果線程一直處于死循環(huán)狀態(tài),CPU 調用會進行線程切換么?


死循環(huán)不會讓出 CPU,除非操作系統(tǒng)時間片到期,但死循環(huán)會不斷向系統(tǒng)申請時間片,直到系統(tǒng)沒有空閑時間做別的事情。


這個問題在 stackoverflow 也有人提問:why does an infinite loop of the unintended kind increase the CPU use?


地址:https://stackoverflow.com/questions/2846165/why-does-an-infinite-loop-of-the-unintended-kind-increase-the-cpu-use


5、頻繁 Young GC 會不會引起 CPU 使用率飚升?


會的。


Young GC 本身是 JVM 進行垃圾回收的操作,會計算內存和調用寄存器,頻繁 Young GC 一定是會占用 CPU。


之前有個一個案例,for 循環(huán)從數據庫查詢數據集合,二次封裝新的數據集合,這時如果量比較大時,內存沒有足夠的空間存儲,那么 JVM 就會 GC 回收那些不再使用的數據,因此量大的時候,就會收到 CPU 使用率報警。


6、線程數很高的應用,CPU 使用率一定高么?


不會。


通過 jstack 查看系統(tǒng)線程狀態(tài),查看整個線程數很多,但 Runable 和 Running 狀態(tài)的線程不多,這時 CPU 使用率不一定會高。


之前有過一個案例,查看系統(tǒng)線程數 1000+,jstack 分析 900多個線程是 BLOCKED 和 WAITING 狀態(tài)的,這種線程是不會占用 CPU 的。


如果線程數很高,其實大多數原因是死鎖,大量線程處于 BLOCKED 和 WAITING 狀態(tài)。


7、CPU 使用率高的應用,線程數一定高么?


不會。


同上,CPU 使用率高的關鍵因素還是計算密集型操作,一個線程如果有大量計算,也會造成 CPU 使用率高,也是現在為什么一個大數據腳本任務,要大規(guī)模集群共同運算才能運行的原因。


8、BLOCKED 狀態(tài)的線程會不會引起 CPU 使用率飚升?


不一定。


CPU使用率的飆升,更多是因為上下文的切換或者runnable狀態(tài)線程過多導致。Blocked狀態(tài),未必會引起CPU上升。


9、分時操作系統(tǒng) CPU us高或者sy高是什么意思?


通過top命令,可以觀察到CPU的us,sy值,示例如下:

關于CPU使用率飆升,我們需要了解什么?

  • us 用戶空間占用CPU百分比,簡單來說,us高是因為程序導致的,通過分析線程堆棧,可以很容易的定位到問題線程。


  • sy 內核空間占用CPU百分比,sy高的時候,如果是程序問題導致,基本是因為線程上下文切換造成的。

CPU飆升線程定位示例:

public class cpuTest {
public static void main(String args[]){
for(int i=0;i<10;i++){
new Thread(){
public void run(){
try{
Thread.sleep(100000);
}catch(Exception e){}
}
}.start();
}
Thread t=new Thread(){
public void run(){
int i=0;
while(true){
i=(i++)/100;
}
}
};
t.setName("Busiest Thread");
t.start();
}
}


步驟1: 執(zhí)行top -c ,顯示進程運行信息列表,鍵入P (大寫p),進程按照CPU使用率排序,最耗CPU的進程PID為18207

步驟2:首先我們可以通過top -Hp <pid>來看這個進程里所有線程的cpu消耗情況

$ top -Hp 18207top - 19:11:43 up 573 days,  2:43,  2 users,  load average: 3.03, 3.03, 3.02Tasks:  44 total,   1 running,  43 sleeping,   0 stopped,   0 zombieCpu(s): 18.8%us,  0.0%sy,  0.0%ni, 81.1%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%stMem:  99191752k total, 98683576k used,   508176k free,   128248k buffersSwap:  1999864k total,   191064k used,  1808800k free, 17413760k cached PID  USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND18250 admin     20   0 26.1g  28m  10m R 99.9  0.0   0:19.50 java Test18207 admin     20   0 26.1g  28m  10m S  0.0  0.0   0:00.00 java Test18208 admin     20   0 26.1g  28m  10m S  0.0  0.0   0:00.09 java Test18209 admin     20   0 26.1g  28m  10m S  0.0  0.0   0:00.00 java Test18210 admin     20   0 26.1g  28m  10m S  0.0  0.0   0:00.00 java Test18211 admin     20   0 26.1g  28m  10m S  0.0  0.0   0:00.00 java

cpu最高的線程是pid為18250的線程,占了99.9%

步驟3:printf “%x\n” 18250將線程PID轉化為16進制

$ printf “%x\n” 18250

0X47A

...

步驟4:jstack 18207|grep'0X47A'-C5 --color找出進程中消耗CPU最多的線程棧

$ jstack 18207|grep'0X47A'-C5 --colorFull thread dump OpenJDK 64-Bit Server VM (25.66-b60 mixed mode):"Attach Listener" #30 daemon prio=9 os_prio=0 tid=0x00007fb90be13000 nid=0x47d7 waiting on condition [0x0000000000000000]   java.lang.Thread.State: RUNNABLE"DestroyJavaVM" #29 prio=5 os_prio=0 tid=0x00007fb96245b800 nid=0x4720 waiting on condition [0x0000000000000000]   java.lang.Thread.State: RUNNABLE"Busiest Thread" #28 prio=5 os_prio=0 tid=0x00007fb91498d000 nid=0x474a runnable [0x00007fb9065fe000]   java.lang.Thread.State: RUNNABLE    at Test$2.run(Test.java:18)"Thread-9" #27 prio=5 os_prio=0 tid=0x00007fb91498c800 nid=0x4749 waiting on condition [0x00007fb906bfe000]   java.lang.Thread.State: TIMED_WAITING (sleeping)    at java.lang.Thread.sleep(Native Method)    at Test$1.run(Test.java:9)...

因此,最耗cpu的線程是Busiest Thread


日常程序中常見的耗CPU的操作:

1、頻繁GC,訪問量高時,有可能造成頻繁的GC、甚至FGC。當調用量大時,內存分配過快,就會造成GC線程不停的執(zhí)行,導致CPU飆高

2、序列化與反序列化,后文中舉了一個真實的案例,程序執(zhí)行xml解析的時,調用量增大的情況下,導致了CPU被打滿

3、加密解密

4、正則表達式校驗,曾經線上發(fā)生一次血案,正則校驗將CPU打滿。大概原因是:Java 正則表達式使用的引擎實現是 NFA 自動機,這種引擎在進行字符匹配會發(fā)生回溯(backtracking)

5、線程上下文切換、當啟動了很多線程,而這些線程都處于不斷的阻塞狀態(tài)(鎖等待、IO等待等)和執(zhí)行狀態(tài)的變化過程中。當鎖競爭激烈時,很容易出現這種情況

6、某些線程在做無阻塞的運算,簡單的例子while(true)中不停的做運算,沒有任何阻塞。寫程序時,如果需要做很久的計算,可以適當將程序sleep下

7、Excel 導出事件


頻繁GC案例

案例背景:網關服務進行控制單個url訪問次數限流,CPU過若干天后飆升到80%,重啟服務過若干天后又再次飆升到80%

分析過程:通過上述方法進行線程棧定位,并進行內存堆分析,發(fā)現pathRaterLimiterConcurrentHashMap對象占用大量堆空間,找到程序中對應的filter過濾器代碼如下

public HttpRequestMessage apply(HttpRequestMessage request) {
String requestPath = request.getPath();
if (pathRaterLimiterConcurrentHashMap.containsKey(requestPath)) {
if (!pathRaterLimiterConcurrentHashMap.get(requestPath).tryAcquire()) {
log.warn("too many request:"+requestPath+",time:"+System.currentTimeMillis());
SessionContext context = request.getContext();
if(requestPath!=null && requestPath.equals("/voting/selection")){
context.setEndpoint(VoteTimeOutEndpoint.class.getCanonicalName());
}
//context.setEndpoint(ManyRequestsEndpoint.class.getCanonicalName());
request.setPath("/404.html");
context.setRouteVIP("limit-api");
}
} else {
pathRaterLimiterConcurrentHashMap.put(requestPath, RateLimiter.create(limit));
pathRaterLimiterConcurrentHashMap.get(requestPath).tryAcquire();
}
return request;
}

分析發(fā)現當url不斷增加時,map的k-v會不斷增加,以至于最后頻繁觸發(fā)fullgc,導致cpu飆升。

解決方案:跑一個定時任務線程定期清理map中的對象,后采用LRU算法重寫一個maputil,定時清理過期的key值。


正則表達式案例

案例背景:前幾天線上一個項目監(jiān)控信息突然報告異常,上到機器上后查看相關資源的使用情況,發(fā)現 CPU 利用率將近 100%。

分析過程:

通過 Java 自帶的線程 Dump 工具,我們導出了出問題的堆棧信息。我們可以看到所有的堆棧都指向了一個名為 validateUrl 的方法,這樣的報錯信息在堆棧中一共超過 100 處。通過排查代碼,我們知道這個方法的主要功能是校驗 URL 是否合法。

很奇怪,一個正則表達式怎么會導致 CPU 利用率居高不下。為了弄清楚復現問題,我們將其中的關鍵代碼摘抄出來,做了個簡單的單元測試。

public static void main(String[] args) {
String badRegex = "^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)" +
"(([A-Za-z0-9-~]+).)+([A-Za-z0-9-~\\\\/])+$";
String bugUrl = "http://www.fapiao.com/dddp-web/pdf/download?" +
"request=6e7JGxxxxx4ILd-kExxxxxxxqJ4-CHLmqVnenXC692m7" +
"4H38sdfdsazxcUmfcOH2fAfY1Vw__%5EDadIfJgiEf";
if (bugUrl.matches(badRegex)) {
System.out.println("match!!");
} else {
System.out.println("no match!!");
}
}

正則表達式分為三部分:

第一部分匹配 http 和 https 協(xié)議,第二部分匹配 www. 字符,第三部分匹配許多字符。我看著這個表達式發(fā)呆了許久,也沒發(fā)現沒有什么大的問題。

其實這里導致 CPU 使用率高的關鍵原因就是:Java 正則表達式使用的引擎實現是 NFA 自動機,這種正則表達式引擎在進行字符匹配時會發(fā)生回溯(backtracking)。而一旦發(fā)生回溯,那其消耗的時間就會變得很長,有可能是幾分鐘,也有可能是幾個小時,時間長短取決于回溯的次數和復雜度。

正則表達式是一個很方便的匹配符號,但要實現這么復雜,功能如此強大的匹配語法,就必須要有一套算法來實現,而實現這套算法的東西就叫做正則表達式引擎。簡單地說,實現正則表達式引擎的有兩種方式:DFA 自動機(Deterministic Final Automata 確定型有窮自動機)和 NFA 自動機(Non deterministic Finite Automaton 不確定型有窮自動機)。

對于這兩種自動機,他們有各自的區(qū)別,這里并不打算深入將它們的原理。簡單地說,DFA 自動機的時間復雜度是線性的,更加穩(wěn)定,但是功能有限。而 NFA 的時間復雜度比較不穩(wěn)定,有時候很好,有時候不怎么好,好不好取決于你寫的正則表達式。

解決方案:

其實在正則表達式中有這么三種模式:貪婪模式、懶惰模式、獨占模式。在關于數量的匹配中,有 + ? * {min,max} 四種,如果只是單獨使用,那么它們就是貪婪模式。

如果在他們之后加多一個 ? 符號,那么原先的貪婪模式就會變成懶惰模式,即盡可能少地匹配。但是懶惰模式還是會發(fā)生回溯現象的。

如果在他們之后加多一個 + 符號,那么原先的貪婪模式就會變成獨占模式,即盡可能多地匹配,但是不回溯。

String goodRegex = "^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)" +
"(([A-Za-z0-9-~]+).)++([A-Za-z0-9-~\\\\/])+$";


Excel導出案例

案例背景:京東某系統(tǒng)使用了poi-ooxml-3.5-final做excel導出功能。起初使用該版本的poi的HSSF配合多線程生成excel,沒有任何問題,后來改成了XSSF生成后上線,導出3w條數據時,cpu使用率達到了100%,內存達到了100%

關于CPU使用率飆升,我們需要了解什么?

關于CPU使用率飆升,我們需要了解什么?

關于CPU使用率飆升,我們需要了解什么?

由于cpu使用率打爆,內存打爆,整個服務器處于拒絕服務狀態(tài),而呈現到前端則是應用系統(tǒng)大部分卡死。于是業(yè)務方不斷反復點擊導出按鈕,狀況不斷擴大到集群內其他機器上,導致集群出現雪崩現象。

分析過程

由于服務器已經被打死,內存那么高,根本無法dump線上堆內存,甚至連jstack查看線程棧都無法使用。因此嘗試在測試機復盤

關于CPU使用率飆升,我們需要了解什么?

可見eden空間的s0和s1已經無法交換了,eden空間已經完全打滿,old空間也一樣打滿,yong gc和full gc都非常頻繁,cpu自然使用率高了,不過不足以打滿整個cpu!現在目前定位到了fullgc沒有回收垃圾,那么需要找到內存打滿和為啥沒回收的原因。要想找到內存打滿的原因肯定需要分析heap空間對象。

由于問題出現在導出報表,并且已知升級了版本并且改成了單線程導出就解決了,同時之前使用HSSF的時候并沒有出現問題,也證明了業(yè)務代碼沒有問題,問題出現在XSSF的版本和多線程上。所以本地可以模擬poi-ooxml-3.5-FINAL的XSSF進行大量數據的導出實驗,同時需要進行多線程導出。

在本地mock數據可以使用簡單的大量對象構成的結構進行導出,線上30個列導出,本地測試5個列,線上是本地的6倍,線上的每一行的數據量必然要比本地的數據量大很多。同時懷疑是poi-ooxml-3.5-FINAL內存泄露或內存管理出現的問題,那么其實不需要4g內存,在2g的內存下壓榨到死看看heap中大量的對象是不是poi相關的就可以了。然后再升級下版本,繼續(xù)壓榨一下看看會不會壓死即可。

public static void main(String[] args) {
int size = 500000;
List<User> users = new ArrayList<>(size);
User user;
for (int i = 0; i < size; i++) {
user = new User();
user.setId(Integer.toUnsignedLong(i));
user.setAge(i + 10);
user.setName("user" + i);
user.setRemark(System.currentTimeMillis() + "");
user.setSex("男");
users.add(user);
}

new Thread(() -{
String[] columnName = {"用戶id", "姓名", "年齡", "性別", "備注"};
Object[][] data = new Object[size][5];
int index = 0;
for (User u : users) {
data[index][0] = u.getId();
data[index][1] = u.getName();
data[index][2] = u.getAge();
data[index][3] = u.getSex();
data[index][4] = u.getRemark();
index++;
}
XSSFWorkbook xssfWorkbook = generateExcel("test", "test", columnName, data);
}).start();
try {
Thread.currentThread().join();//等待子線程結束
} catch (InterruptedException e) {
e.printStackTrace();
}
}


模擬現象與線上情況類似,大量cpu占用在XSSFCell.setCellValue中,生成excel generateExcel就占據了所有的cpu,堆信息全是POI對象

關于CPU使用率飆升,我們需要了解什么?

關于CPU使用率飆升,我們需要了解什么?

這里還需要注意的是,需要驗證poi-ooxml-3.5-FINAL在多線程情況下是否會出現這個問題,驗證很簡單,把new Thread去掉,直接在主線程導出。這里直接說明實驗結果,new Thread去了依然內存爆滿!

解決方案

查看poi官網的change log http://poi.apache.org/changes.html ,既然3.5-FINAL的XSSF有問題,向上查找3.5-FINAL之后的XSSF相關字樣的信息,會發(fā)現在3.6中memory usage optimization in xssf - avoid creating parentless xml beans,xxsf進行中做了內存優(yōu)化 - 避免了創(chuàng)建無父類的xml bean對象


所以得出結論,升級poi-oxxml版本到3.6或者更高版本!


   
           
來源:技術讓夢想更偉大

免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯系我們,謝謝!

21ic電子網

掃描二維碼,關注更多精彩內容

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯系該專欄作者,如若文章內容侵犯您的權益,請及時聯系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或將催生出更大的獨角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數字化轉型技術解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術公司SODA.Auto推出其旗艦產品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據媒體報道,騰訊和網易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數據產業(yè)博覽會開幕式在貴陽舉行,華為董事、質量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數據產業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數字世界的話語權最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經營業(yè)績穩(wěn)中有升 落實提質增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數字經濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術學會聯合牽頭組建的NVI技術創(chuàng)新聯盟在BIRTV2024超高清全產業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現場 NVI技術創(chuàng)新聯...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯合招商會上,軟通動力信息技術(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術
關閉
關閉