java大對象引發的頻繁full gc及cpu飆高的調優歷程

問題描述

linux系統,執行top命令

-bash-4.2# top

top - 08:57:47 up 147 days, 23:08,  2 users,  load average: 67.32, 60.83, 58.97
Tasks: 1310 total,   2 running, 1307 sleeping,   0 stopped,   1 zombie
%Cpu(s): 48.9 us,  1.2 sy,  0.0 ni, 49.6 id,  0.2 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 56109344+total,  1076992 free, 49768768 used, 51024771+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 50798355+avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 70853 root      20   0 71.947g 0.031t  20088 S  6978  6.0  11566:28 java
 30632 root      20   0 43.589g 1.229g  12108 S  68.9  0.2   9335:55 java
  2135 root      20   0  160600  46492    832 R  13.7  0.0   4648:46 statmon
  4219 root      20   0 99.287g 2.030g  11576 S   2.9  0.4 747:32.73 java
 59325 root      20   0  158752   3552   1588 R   2.5  0.0   0:00.70 top
 57806 root       0 -20       0      0      0 S   1.9  0.0   0:01.70 kworker/18:2H
    10 root      20   0       0      0      0 S   0.6  0.0 313:00.57 rcu_sched
 129997 root      20   0 44.030g 3.102g  11992 S   0.6  0.6   2840:59 java 

上述70853這個進程,佔用過高的cpu,而且程序運行緩慢,重啓之後就正常了,運行一段時間之後又是這樣。

問題排查

線程使用cpu排查

首先遇到cpu彪高的問題,我們要先定位是哪些線程在持有這些cpu,採用top -Hp pid命令可以查看線程的cpu使用情況

-bash-4.2# top -Hp 70853

top - 09:03:01 up 147 days, 23:13,  2 users,  load average: 39.88, 48.75, 54.03
Threads: 601 total,  93 running, 508 sleeping,   0 stopped,   0 zombie
%Cpu(s): 43.2 us, 11.3 sy,  0.0 ni, 45.0 id,  0.3 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 56109344+total,   878212 free, 49768532 used, 51044672+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 50798249+avail Mem 

   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND                                                                                                                                        
 70866 root      20   0 71.948g 0.032t  20104 R 80.4  6.0  79:06.89 java
 70906 root      20   0 71.948g 0.032t  20104 R 80.4  6.0  78:58.43 java
 70921 root      20   0 71.948g 0.032t  20104 R 80.4  6.0  78:58.79 java
 70856 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:01.84 java
 70859 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  78:57.72 java
 70863 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:06.32 java
 70865 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:09.23 java
 70872 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  78:52.49 java
 70875 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:02.85 java
 70877 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:04.04 java
 70879 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:07.52 java
 70881 root      20   0 71.948g 0.032t  20104 R 80.1  6.0  79:00.38 java

上述是一系列的該進程下的線程使用cpu的情況,並且省略了一部分,將上述pid下面的十進制的線程id轉成16進制,如下:

-bash-4.2# printf "%x\n" 70856
114c8

線程堆棧排查

上述排查到了哪些線程使用cpu率高,那麼這些cpu到底在幹什麼呢?可以使用jstack pid命令查看jvm的線程堆棧

 

gc動態監控

上述線程堆棧查看,發現線程大部分在執行gc回收,這就奇怪了,爲什麼一直在gc,具體查看gc的情況,可以使用jstat -gcutil pid

-bash-4.2# jstat -gcutil 70853 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  99.90  99.98  92.68  88.47  11534 1351.766  2466 4811.444 6163.210
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2467 4813.394 6165.160
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2467 4813.394 6165.160
  0.00   0.00  99.96 100.00  92.68  88.47  11534 1351.766  2468 4815.426 6167.192
  0.00   0.00  99.96 100.00  92.68  88.47  11534 1351.766  2468 4815.426 6167.192
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2469 4817.455 6169.221
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2469 4817.455 6169.221
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2470 4819.330 6171.096
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2470 4819.330 6171.096
  0.00   0.00 100.00 100.00  92.68  88.47  11534 1351.766  2471 4821.328 6173.094
  0.00   0.00 100.00 100.00  92.68  88.47  11534 1351.766  2471 4821.328 6173.094
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2472 4823.229 6174.995
  0.00   0.00  99.99 100.00  92.68  88.47  11534 1351.766  2472 4823.229 6174.995
  0.00   0.00  99.97  99.97  92.68  88.47  11534 1351.766  2473 4825.121 6176.888
  0.00   0.00  99.97  99.97  92.68  88.47  11534 1351.766  2473 4825.121 6176.888
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2474 4827.092 6178.859
  0.00   0.00 100.00  99.99  92.68  88.47  11534 1351.766  2474 4827.092 6178.859
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2475 4829.041 6180.807
  0.00   0.00  99.96  99.99  92.68  88.47  11534 1351.766  2475 4829.041 6180.807 

S0,S1:新生代的兩個倖存者區

E:新生代的伊甸園區

O:老年代

YGC:新生代gc

FGC:full gc 

上述主要指標解釋

S0,S1:新生代的兩個倖存者區

E:新生代的伊甸園區

O:老年代

YGC:新生代gc

FGC:full gc 

通過上述排查,可以發現,一直在full gc,並且老年代內存並沒有回收,致此,內存泄露了。

堆內存查看佔用內存的對象

採用jvisiual命令查看heap dump

上圖所示被ScheduledThreadPoolExecutor中的優先級隊列持有。

代碼分析

邏輯是這樣的,視圖庫接收前端實卡傳來的圖片,高清圖片,大概有幾百kb到十幾M不等,採用1400標準接口,內部採用線程池來異步處理傳來的圖片及結構化數據,線程池代碼如下所示:

private ScheduledExecutorService threadPool ;

private BasicService basicService;

@PostConstruct
public void vcnLogin(){
    BasicService basicService = new BasicServiceImpl();
    basicService.setLogPath("/opt/");
    int result = basicService.initial();
    int result1 = basicService.login("vcnesdk02", "Huawei12#$", "34.8.20.132", 9900);
    logger.info("vcn login result: "+result1);

    threadPool = Executors.newScheduledThreadPool(50);
}

 來的圖ScheduledThreadPoolExecutor線程池默認採用DelayedWorkQueue,是一種延時的優先級隊列,在這裏需要關注的是無界隊列。

項目剛開始運行沒什麼問題,在早高峯和晚高峯的時候,數據突然多了起來,full gc發生頻繁,因爲採用的並行GC算法,佔用資源過多,導致線程池處理越來越慢,隊列積壓的任務越來越多,老年代空間越來越小,造成full gc越來越頻繁,gc越頻繁,任務處理越慢,最終導致上圖所示的老年代都滿了,但是full gc之後,空間一直不下降。數據接入曲線圖如下:

圖中顯示早高峯7點半的時候,達到了數據洪峯,系統扛不住了,於是老年代滿了,頻繁full gc,導致系統越來越慢,吞吐量下降。

解決方案

略,隨意。

 

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