技能篇:linux服務效能問題排查及jvm調優思路
阿新 • • 發佈:2022-04-01
只要業務邏輯程式碼寫正確,處理好業務狀態在多執行緒的併發問題,很少會有調優方面的需求。最多就是在效能監控平臺發現某些介面的呼叫耗時偏高,然後再發現某一SQL或第三方介面執行超時之類的。如果你是負責中介軟體或IM通訊相關專案開發,或許就需要偏向CPU、磁碟、網路及記憶體方面的問題排查及調優技能
- CPU過高,怎麼排查問題
- linux記憶體
- 磁碟IO
- 網路IO
- java 應用記憶體洩漏和頻繁 GC
- java 執行緒問題排查
- 常用 jvm 啟動引數調優
介紹一下linux 調優相關命令,傳送門:開發必備linux命令大全-穩賺不虧
關注公眾號,一起交流,微信搜一搜: 潛行前行
linux CPU 過高,怎麼排查問題
CPU 指標解析
- 平均負載
- 平均負載等於邏輯 CPU 個數,表示每個 CPU 都恰好被充分利用。如果平均負載大於邏輯 CPU 個數,則負載比較重
- 程序上下文切換
- 無法獲取資源而導致的自願上下文切換
- 被系統強制排程導致的非自願上下文切換
- CPU 使用率
- 使用者 CPU 使用率,包括使用者態 CPU 使用率(user)和低優先順序使用者態 CPU 使用率(nice),表示 CPU 在使用者態執行的時間百分比。使用者 CPU 使用率高,通常說明有應用程式比較繁忙
- 系統 CPU 使用率,表示 CPU 在核心態執行的時間百分比(不包括中斷),系統 CPU 使用率高,說明核心比較繁忙
- 等待 I/O 的 CPU 使用率,通常也稱為 iowait,表示等待 I/O 的時間百分比。iowait 高,說明系統與硬體裝置的 I/O 互動時間比較長
- 軟中斷和硬中斷的 CPU 使用率,分別表示核心呼叫軟中斷處理程式、硬中斷處理程式的時間百分比。它們的使用率高,表明系統發生了大量的中斷
檢視系統的平均負載
$ uptime
10:54:52 up 1124 days, 16:31, 6 users, load average: 3.67, 2.13, 1.79
- 10:54:52 是當前時間;up 1124 days, 16:31 是系統執行時間; 6 users 則是正在登入使用者數。而最後三個數字依次是過去 1 分鐘、5 分鐘、15 分鐘的平均負載(Load Average)。平均負載是指單位時間內,系統處於可執行狀態和不可中斷狀態的平均程序數
- 當平均負載高於 CPU 數量 70% 的時候,就應該分析排查負載高的問題。一旦負載過高,就可能導致程序響應變慢,進而影響服務的正常功能
- 平均負載與 CPU 使用率關係
- CPU 密集型程序,使用大量 CPU 會導致平均負載升高,此時這兩者是一致的
- I/O 密集型程序,等待 I/O 也會導致平均負載升高,但 CPU 使用率不一定很高
- 大量等待 CPU 的程序排程也會導致平均負載升高,此時的 CPU 使用率也會比較高
CPU 上下文切換
- 程序上下文切換:
- 程序的執行空間可以分為核心空間和使用者空間,當代碼發生系統呼叫時(訪問受限制的資源),CPU 會發生上下文切換,系統呼叫結束時,CPU 則再從核心空間換回使用者空間。一次系統呼叫,兩次 CPU 上下文切換
- 系統平時會按一定的策略呼叫程序,會導致程序上下文切換
- 程序在阻塞等到訪問資源時,也會發生上下文切換
- 程序通過睡眠函式掛起,會發生上下文切換
- 當有優先順序更高的程序執行時,為了保證高優先順序程序的執行,當前程序會被掛起
- 執行緒上下文切換:
- 同一程序裡的執行緒,它們共享相同的虛擬記憶體和全域性變數資源,執行緒上下文切換時,這些資源不變
- 執行緒自己的私有資料,比如棧和暫存器等,需要在上下文切換時儲存切換
- 中斷上下文切換:
- 為了快速響應硬體的事件,中斷處理會打斷程序的正常排程和執行,轉而呼叫中斷處理程式,響應裝置事件
檢視系統的上下文切換情況:
vmstat 和 pidstat。vmvmstat 可檢視系統總體的指標,pidstat則詳細到每一個程序服務的指標
$ vmstat 2 1
procs --------memory--------- --swap-- --io--- -system-- ----cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 3498472 315836 3819540 0 0 0 1 2 0 3 1 96 0 0
--------
cs(context switch)是每秒上下文切換的次數
in(interrupt)則是每秒中斷的次數
r(Running or Runnable)是就緒佇列的長度,也就是正在執行和等待 CPU 的程序數.當這個值超過了CPU數目,就會出現CPU瓶頸
b(Blocked)則是處於不可中斷睡眠狀態的程序數
# pidstat -w
Linux 3.10.0-862.el7.x86_64 (8f57ec39327b) 07/11/2021 _x86_64_ (6 CPU)
06:43:23 PM UID PID cswch/s nvcswch/s Command
06:43:23 PM 0 1 0.00 0.00 java
06:43:23 PM 0 102 0.00 0.00 bash
06:43:23 PM 0 150 0.00 0.00 pidstat
------各項指標解析---------------------------
PID 程序id
Cswch/s 每秒主動任務上下文切換數量
Nvcswch/s 每秒被動任務上下文切換數量。大量程序都在爭搶 CPU 時,就容易發生非自願上下文切換
Command 程序執行命令
怎麼排查 CPU 過高問題
- 先使用 top 命令,檢視系統相關指標。如需要按某指標排序則 使用
top -o 欄位名
如:top -o %CPU
。-o
可以指定排序欄位,順序從大到小
# top -o %MEM
top - 18:20:27 up 26 days, 8:30, 2 users, load average: 0.04, 0.09, 0.13
Tasks: 168 total, 1 running, 167 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.5 sy, 0.0 ni, 99.1 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
KiB Mem: 32762356 total, 14675196 used, 18087160 free, 884 buffers
KiB Swap: 2103292 total, 0 used, 2103292 free. 6580028 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2323 mysql 20 0 19.918g 4.538g 9404 S 0.333 14.52 352:51.44 mysqld
1260 root 20 0 7933492 1.173g 14004 S 0.333 3.753 58:20.74 java
1520 daemon 20 0 358140 3980 776 S 0.333 0.012 6:19.55 httpd
1503 root 20 0 69172 2240 1412 S 0.333 0.007 0:48.05 httpd
---------各項指標解析---------------------------------------------------
第一行統計資訊區
18:20:27 當前時間
up 25 days, 17:29 系統執行時間,格式為時:分
1 user 當前登入使用者數
load average: 0.04, 0.09, 0.13 系統負載,三個數值分別為 1分鐘、5分鐘、15分鐘前到現在的平均值
Tasks:程序相關資訊
running 正在執行的程序數
sleeping 睡眠的程序數
stopped 停止的程序數
zombie 殭屍程序數
Cpu(s):CPU相關資訊
%us:表示使用者空間程式的cpu使用率(沒有通過nice排程)
%sy:表示系統空間的cpu使用率,主要是核心程式
%ni:表示使用者空間且通過nice排程過的程式的cpu使用率
%id:空閒cpu
%wa:cpu執行時在等待io的時間
%hi:cpu處理硬中斷的數量
%si:cpu處理軟中斷的數量
Mem 記憶體資訊
total 實體記憶體總量
used 使用的實體記憶體總量
free 空閒記憶體總量
buffers 用作核心快取的記憶體量
Swap 記憶體資訊
total 交換區總量
used 使用的交換區總量
free 空閒交換區總量
cached 緩衝的交換區總量
- 找到相關程序後,我們則可以使用
top -Hp pid
或pidstat -t -p pid
命令檢視程序具體執行緒使用 CPU 情況,從而找到具體的導致 CPU 高的執行緒- %us 過高,則可以在對應 java 服務根據執行緒ID檢視具體詳情,是否存在死迴圈,或者長時間的阻塞呼叫。java 服務可以使用 jstack
- 如果是 %sy 過高,則先使用 strace 定位具體的系統呼叫,再定位是哪裡的應用程式碼導致的
- 如果是 %si 過高,則可能是網路問題導致軟中斷頻率飆高
- %wa 過高,則是頻繁讀寫磁碟導致的。
linux 記憶體
檢視記憶體使用情況
- 使用 top 或者 free、vmstat 命令
# top
top - 18:20:27 up 26 days, 8:30, 2 users, load average: 0.04, 0.09, 0.13
Tasks: 168 total, 1 running, 167 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.3 us, 0.5 sy, 0.0 ni, 99.1 id, 0.0 wa, 0.0 hi, 0.1 si, 0.0 st
KiB Mem: 32762356 total, 14675196 used, 18087160 free, 884 buffers
KiB Swap: 2103292 total, 0 used, 2103292 free. 6580028 cached Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
2323 mysql 20 0 19.918g 4.538g 9404 S 0.333 14.52 352:51.44 mysqld
1260 root 20 0 7933492 1.173g 14004 S 0.333 3.753 58:20.74 java
....
- bcc-tools 軟體包裡的 cachestat 和 cachetop、memleak
- achestat 可檢視整個系統快取的讀寫命中情況
- cachetop 可檢視每個程序的快取命中情況
- memleak 可以用檢查 C、C++ 程式的記憶體洩漏問題
free 命令記憶體指標
# free -m
total used free shared buffers cached
Mem: 32107 30414 1692 0 1962 8489
-/+ buffers/cache: 19962 12144
Swap: 0 0 0
- shared 是共享記憶體的大小, 一般系統不會用到,總是0
- buffers/cache 是快取和緩衝區的大小,buffers 是對原始磁碟塊的快取,cache 是從磁碟讀取檔案系統裡檔案的頁快取
- available 是新程序可用記憶體的大小
記憶體 swap 過高
Swap 其實就是把一塊磁碟空間或者一個本地檔案,當成記憶體來使用。swap 換出,把程序暫時不用的記憶體資料儲存到磁碟中,並釋放這些資料佔用的記憶體。swap 換入,在程序再次訪問這些記憶體的時候,把它們從磁碟讀到記憶體中來
- swap 和 記憶體回收的機制
- 記憶體的回收既包括了檔案頁(記憶體對映獲取磁碟檔案的頁)又包括了匿名頁(程序動態分配的記憶體)
- 對檔案頁的回收,可以直接回收快取,或者把髒頁寫回磁碟後再回收
- 而對匿名頁的回收,其實就是通過 Swap 機制,把它們寫入磁碟後再釋放記憶體
- swap 過高會造成嚴重的效能問題,頁失效會導致頻繁的頁面在記憶體和磁碟之間交換
- 一般線上的伺服器的記憶體都很大,可以禁用 swap
- 可以設定 /proc/sys/vm/min_free_kbytes,來調整系統定期回收記憶體的閾值,也可以設定 /proc/sys/vm/swappiness,來調整檔案頁和匿名頁的回收傾向
linux 磁碟I/O 問題
檔案系統和磁碟
- 磁碟是一個儲存裝置(確切地說是塊裝置),可以被劃分為不同的磁碟分割槽。而在磁碟或者磁碟分割槽上,還可以再建立檔案系統,並掛載到系統的某個目錄中。系統就可以通過這個掛載目錄來讀寫檔案
- 磁碟是儲存資料的塊裝置,也是檔案系統的載體。所以,檔案系統確實還是要通過磁碟,來保證資料的持久化儲存
- 系統在讀寫普通檔案時,I/O 請求會首先經過檔案系統,然後由檔案系統負責,來與磁碟進行互動。而在讀寫塊裝置檔案時,會跳過檔案系統,直接與磁碟互動
- linux 記憶體裡的 Buffers 是對原始磁碟塊的臨時儲存,也就是用來快取磁碟的資料,通常不會特別大(20MB 左右)。核心就可以把分散的寫集中起來(優化磁碟的寫入)
- linux 記憶體裡的 Cached 是從磁碟讀取檔案的頁快取,也就是用來快取從檔案讀寫的資料。下次訪問這些檔案資料時,則直接從記憶體中快速獲取,而不再次訪問磁碟
磁碟效能指標
- 使用率,是指磁碟處理 I/O 的時間百分比。過高的使用率(比如超過 80%),通常意味著磁碟 I/O 存在效能瓶頸。
- 飽和度,是指磁碟處理 I/O 的繁忙程度。過高的飽和度,意味著磁碟存在嚴重的效能瓶頸。當飽和度為 100% 時,磁碟無法接受新的 I/O 請求。
- IOPS(Input/Output Per Second),是指每秒的 I/O 請求數
- 吞吐量,是指每秒的 I/O 請求大小
- 響應時間,是指 I/O 請求從發出到收到響應的間隔時間
IO 過高怎麼找問題,怎麼調優
- 檢視系統磁碟整體 I/O
# iostat -x -k -d 1 1
Linux 4.4.73-5-default (ceshi44) 2021年07月08日 _x86_64_ (40 CPU)
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
sda 0.08 2.48 0.37 11.71 27.80 507.24 88.53 0.02 1.34 14.96 0.90 0.09 0.10
sdb 0.00 1.20 1.28 16.67 30.91 647.83 75.61 0.17 9.51 9.40 9.52 0.32 0.57
------
rrqm/s: 每秒對該裝置的讀請求被合併次數,檔案系統會對讀取同塊(block)的請求進行合併
wrqm/s: 每秒對該裝置的寫請求被合併次數
r/s: 每秒完成的讀次數
w/s: 每秒完成的寫次數
rkB/s: 每秒讀資料量(kB為單位)
wkB/s: 每秒寫資料量(kB為單位)
avgrq-sz: 平均每次IO操作的資料量(扇區數為單位)
avgqu-sz: 平均等待處理的IO請求佇列長度
await: 平均每次IO請求等待時間(包括等待時間和處理時間,毫秒為單位)
svctm: 平均每次IO請求的處理時間(毫秒為單位)
%util: 採用週期內用於IO操作的時間比率,即IO佇列非空的時間比率
- 檢視程序級別 I/O
# pidstat -d
Linux 3.10.0-862.el7.x86_64 (8f57ec39327b) 07/11/2021 _x86_64_ (6 CPU)
06:42:35 PM UID PID kB_rd/s kB_wr/s kB_ccwr/s Command
06:42:35 PM 0 1 1.05 0.00 0.00 java
06:42:35 PM 0 102 0.04 0.05 0.00 bash
------
kB_rd/s 每秒從磁碟讀取的KB
kB_wr/s 每秒寫入磁碟KB
kB_ccwr/s 任務取消的寫入磁碟的KB。當任務截斷髒的pagecache的時候會發生
Command 程序執行命令
- 當使用 pidstat -d 定位到哪個應用服務時,接下來則需要使用 strace 和 lsof 定位是那些程式碼在讀寫磁盤裡的哪些檔案,導致IO高的原因
$ strace -p 18940
strace: Process 18940 attached
...
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f7aee9000
mmap(NULL, 314576896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0f682e8000
write(3, "2018-12-05 15:23:01,709 - __main"..., 314572844
) = 314572844
munmap(0x7f0f682e8000, 314576896) = 0
write(3, "\n", 1) = 1
munmap(0x7f0f7aee9000, 314576896) = 0
close(3) = 0
stat("/tmp/logtest.txt.1", {st_mode=S_IFREG|0644, st_size=943718535, ...}) = 0
- strace 命令輸出可以看到程序18940 正在往檔案 /tmp/logtest.txt.1 寫入300m
$ lsof -p 18940
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 18940 root cwd DIR 0,50 4096 1549389 /
…
java 18940 root 2u CHR 136,0 0t0 3 /dev/pts/0
java 18940 root 3w REG 8,1 117944320 303 /tmp/logtest.txt
----
FD 表示檔案描述符號,TYPE 表示檔案型別,NODE NAME 表示檔案路徑
- lsof 也可以看出程序18940 以每次 300MB 的速度往 /tmp/logtest.txt 寫入
linux 網路I/O 問題
當一個網路幀到達網絡卡後,網絡卡會通過 DMA 方式,把這個網路包放到收包佇列中;然後通過硬中斷,告訴中斷處理程式已經收到了網路包。接著,網絡卡中斷處理程式會為網路幀分配核心資料結構(sk_buff),並將其拷貝到 sk_buff 緩衝區中;然後再通過軟中斷,通知核心收到了新的網路幀。核心協議棧從緩衝區中取出網路幀,並通過網路協議棧,從下到上逐層處理這個網路幀
- 硬中斷:與系統相連的外設(比如網絡卡、硬碟)自動產生的。主要是用來通知作業系統系統外設狀態的變化。比如當網絡卡收到資料包的時候,就會發出一個硬中斷
- 軟中斷:為了滿足實時系統的要求,中斷處理應該是越快越好。linux為了實現這個特點,當中斷髮生的時候,硬中斷處理那些短時間就可以完成的工作,而將那些處理事件比較長的工作,交給軟中斷來完成
網路I/O指標
- 頻寬,表示鏈路的最大傳輸速率,單位通常為 b/s (位元 / 秒)
- 吞吐量,表示單位時間內成功傳輸的資料量,單位通常為 b/s(位元 / 秒)或者 B/s(位元組 / 秒)吞吐量受頻寬限制,而吞吐量 / 頻寬,也就是該網路的使用率
- 延時,表示從網路請求發出後,一直到收到遠端響應,所需要的時間延遲。在不同場景中,這一指標可能會有不同含義。比如,它可以表示,建立連線需要的時間(比如 TCP 握手延時),或一個數據包往返所需的時間(比如 RTT)
- PPS,是 Packet Per Second(包 / 秒)的縮寫,表示以網路包為單位的傳輸速率。PPS 通常用來評估網路的轉發能力,比如硬體交換機,通常可以達到線性轉發(即 PPS 可以達到或者接近理論最大值)。而基於 Linux 伺服器的轉發,則容易受網路包大小的影響
- 網路的連通性
- 併發連線數(TCP 連線數量)
- 丟包率(丟包百分比)
檢視網路I/O指標
- 檢視網路配置
# ifconfig em1
em1 Link encap:Ethernet HWaddr 80:18:44:EB:18:98
inet addr:192.168.0.44 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::8218:44ff:feeb:1898/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:3098067963 errors:0 dropped:5379363 overruns:0 frame:0
TX packets:2804983784 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:1661766458875 (1584783.9 Mb) TX bytes:1356093926505 (1293271.9 Mb)
Interrupt:83
-----
TX 和 RX 部分的 errors、dropped、overruns、carrier 以及 collisions 等指標不為 0 時,
通常表示出現了網路 I/O 問題。
errors 表示發生錯誤的資料包數,比如校驗錯誤、幀同步錯誤等
dropped 表示丟棄的資料包數,即資料包已經收到了 Ring Buffer,但因為記憶體不足等原因丟包
overruns 表示超限資料包數,即網路 I/O 速度過快,導致 Ring Buffer 中的資料包來不及處理(佇列滿)而導致的丟包
carrier 表示發生 carrirer 錯誤的資料包數,比如雙工模式不匹配、物理電纜出現問題等
collisions 表示碰撞資料包數
- 網路吞吐和 PPS
# sar -n DEV 1
Linux 4.4.73-5-default (ceshi44) 2022年03月31日 _x86_64_ (40 CPU)
15時39分40秒 IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s %ifutil
15時39分41秒 em1 1241.00 1022.00 600.48 590.39 0.00 0.00 165.00 0.49
15時39分41秒 lo 636.00 636.00 7734.06 7734.06 0.00 0.00 0.00 0.00
15時39分41秒 em4 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
15時39分41秒 em3 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
15時39分41秒 em2 26.00 20.00 6.63 8.80 0.00 0.00 0.00 0.01
----
rxpck/s 和 txpck/s 分別是接收和傳送的 PPS,單位為包 / 秒
rxkB/s 和 txkB/s 分別是接收和傳送的吞吐量,單位是 KB/ 秒
rxcmp/s 和 txcmp/s 分別是接收和傳送的壓縮資料包數,單位是包 / 秒
- 寬頻
# ethtool em1 | grep Speed
Speed: 1000Mb/s
- 連通性和延遲
# ping www.baidu.com
PING www.a.shifen.com (14.215.177.38) 56(84) bytes of data.
64 bytes from 14.215.177.38: icmp_seq=1 ttl=56 time=53.9 ms
64 bytes from 14.215.177.38: icmp_seq=2 ttl=56 time=52.3 ms
64 bytes from 14.215.177.38: icmp_seq=3 ttl=56 time=53.8 ms
64 bytes from 14.215.177.38: icmp_seq=4 ttl=56 time=56.0 ms
- 統計 TCP 連線狀態工具 ss 和 netstat
[root@root ~]$>#ss -ant | awk '{++S[$1]} END {for(a in S) print a, S[a]}'
LISTEN 96
CLOSE-WAIT 527
ESTAB 8520
State 1
SYN-SENT 2
TIME-WAIT 660
[root@root ~]$>#netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
CLOSE_WAIT 530
ESTABLISHED 8511
FIN_WAIT2 3
TIME_WAIT 809
網路請求變慢,怎麼調優
- 高併發下 TCP 請求變多,會有大量處於 TIME_WAIT 狀態的連線,它們會佔用大量記憶體和埠資源。此時可以優化與 TIME_WAIT 狀態相關的核心選項
- 增大處於 TIME_WAIT 狀態的連線數量 net.ipv4.tcp_max_tw_buckets ,並增大連線跟蹤表的大小 net.netfilter.nf_conntrack_max
- 減小 net.ipv4.tcp_fin_timeout 和 net.netfilter.nf_conntrack_tcp_timeout_time_wait ,讓系統儘快釋放它們所佔用的資源
- 開啟埠複用 net.ipv4.tcp_tw_reuse。這樣,被 TIME_WAIT 狀態佔用的埠,還能用到新建的連線中
- 增大本地埠的範圍 net.ipv4.ip_local_port_range 。這樣就可以支援更多連線,提高整體的併發能力
- 增加最大檔案描述符的數量。可以使用 fs.nr_open 和 fs.file-max ,分別增大程序和系統的最大檔案描述符數
- SYN FLOOD 攻擊,利用 TCP 協議特點進行攻擊而引發的效能問題,可以考慮優化與 SYN 狀態相關的核心選項
- 增大 TCP 半連線的最大數量 net.ipv4.tcp_max_syn_backlog ,或者開啟 TCP SYN Cookies net.ipv4.tcp_syncookies ,來繞開半連線數量限制的問題
- 減少 SYN_RECV 狀態的連線重傳 SYN+ACK 包的次數 net.ipv4.tcp_synack_retries
- 加快 TCP 長連線的回收,優化與 Keepalive 相關的核心選項
- 縮短最後一次資料包到 Keepalive 探測包的間隔時間 net.ipv4.tcp_keepalive_time
- 縮短髮送 Keepalive 探測包的間隔時間 net.ipv4.tcp_keepalive_intvl
- 減少 Keepalive 探測失敗後,一直到通知應用程式前的重試次數 net.ipv4.tcp_keepalive_probes
java 應用記憶體洩漏和頻繁 GC
區分記憶體溢位、記憶體洩漏、記憶體逃逸
- 記憶體洩漏:記憶體被申請後始終無法釋放,導致記憶體無法被回收使用,造成記憶體空間浪費
- 記憶體溢位:指記憶體申請時,記憶體空間不足
- 1-記憶體上限過小
- 2-記憶體載入資料太多
- 3-分配太多記憶體沒有回收,出現記憶體洩漏
- 記憶體逃逸:是指程式執行時的資料,本應在棧上分配,但需要在堆上分配,稱為記憶體逃逸
- java中的物件都是在堆上分配的,而垃圾回收機制會回收堆中不再使用的物件,但是篩選可回收物件,回收物件還有整理記憶體都需要消耗時間。如果能夠通過逃逸分析確定物件不會逃出方法之外,那就可以讓這個物件在棧上分配記憶體,物件所佔用的記憶體就可以隨棧幀出棧而銷燬,就減輕了垃圾回收的壓力
- 執行緒同步本身比較耗時,如果確定一個變數不會逃逸出執行緒,無法被其它執行緒訪問到,那這個變數的讀寫就不會存在競爭,對這個變數的同步措施可以清除
- java 虛擬機器中的原始資料型別(int,long及reference型別等) 都不能再進一步分解,它們稱為標量。如果一個數據可以繼續分解,那它稱為聚合量,java 中最典型的聚合量是物件。如果逃逸分析證明一個物件不會被外部訪問,並且這個物件是可分解的,那程式執行時可能不建立該物件,而改為直接建立它的若干個被方法使用到的成員變數來代替。拆散後的變數便可以被單獨分析與優化,可以各自分別在棧幀或暫存器上分配空間,原本的物件就無需整體分配空間
記憶體洩漏,該如何定位和處理
- 使用
jmap -histo:live [pid]
和jmap -dump:format=b,file=filename [pid]
前者可以統計堆記憶體物件大小和數量,後者可以把堆記憶體 dump 下來 - 啟動引數中指定
-XX:+HeapDumpOnOutOfMemoryError
來儲存OOM時的dump檔案 - 使用 JProfiler 或者 MAT 軟體檢視 heap 記憶體物件,可以更直觀地發現洩露的物件
java執行緒問題排查
java 執行緒狀態
- NEW:對應沒有 Started 的執行緒,對應新生態
- RUNNABLE:對於就緒態和執行態的合稱
- BLOCKED,WAITING,TIMED_WAITING:三個都是阻塞態
- sleep 和 join 稱為WAITING
- sleep 和 join 方法設定了超時時間的,則是 TIMED_WAITING
- wait 和 IO 流阻塞稱為BLOCKED
- TERMINATED: 死亡態
執行緒出現死鎖或者被阻塞
-
jstack –l pid | grep -i –E 'BLOCKED | deadlock'
, 加上引數 -l,jstack 命令可以快速打印出造成死鎖的程式碼
# jstack -l 28764
Full thread dump Java HotSpot(TM) 64-Bit Server VM (13.0.2+8 mixed mode, sharing):
.....
"Thread-0" #14 prio=5 os_prio=0 cpu=0.00ms elapsed=598.37s tid=0x000001b3c25f7000 nid=0x4abc waiting for monitor entry [0x00000061661fe000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.Test$DieLock.run(Test.java:52)
- waiting to lock <0x0000000712d7c230> (a java.lang.Object)
- locked <0x0000000712d7c220> (a java.lang.Object)
at java.lang.Thread.run([email protected]/Thread.java:830)
Locked ownable synchronizers:
- None
"Thread-1" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=598.37s tid=0x000001b3c25f8000 nid=0x1984 waiting for monitor entry [0x00000061662ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.Test$DieLock.run(Test.java:63)
- waiting to lock <0x0000000712d7c220> (a java.lang.Object)
- locked <0x0000000712d7c230> (a java.lang.Object)
at java.lang.Thread.run([email protected]/Thread.java:830)
.....
Found one Java-level deadlock:
=============================
"Thread-0":
waiting to lock monitor 0x000001b3c1e4c480 (object 0x0000000712d7c230, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x000001b3c1e4c080 (object 0x0000000712d7c220, a java.lang.Object),
which is held by "Thread-0"
Java stack information for the threads listed above:
===================================================
"Thread-0":
at com.Test$DieLock.run(Test.java:52)
- waiting to lock <0x0000000712d7c230> (a java.lang.Object)
- locked <0x0000000712d7c220> (a java.lang.Object)
at java.lang.Thread.run([email protected]/Thread.java:830)
"Thread-1":
at com.Test$DieLock.run(Test.java:63)
- waiting to lock <0x0000000712d7c220> (a java.lang.Object)
- locked <0x0000000712d7c230> (a java.lang.Object)
at java.lang.Thread.run([email protected]/Thread.java:830)
Found 1 deadlock.
- 從 jstack 輸出的日誌可以看出執行緒阻塞在 Test.java:52 行,發生了死鎖
常用 jvm 調優啟動引數
- -verbose:gc 輸出每次GC的相關情況
- -verbose:jni 輸出native方法呼叫的相關情況,一般用於診斷jni呼叫錯誤資訊
- -Xms n 指定jvm堆的初始大小,預設為實體記憶體的1/64,最小為1M;可以指定單位,比如k、m,若不指定,則預設為位元組
- -Xmx n 指定jvm堆的最大值,預設為實體記憶體的1/4或者1G,最小為2M;單位與-Xms一致
- -Xss n 設定單個執行緒棧的大小,一般預設為512k
- -XX:NewRatio=4 設定年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設定為4,則年輕代與年老代所佔比值為1:4,年輕代佔整個堆疊的1/5
- -Xmn 設定新生代記憶體大小。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8
- -XX:SurvivorRatio=4 設定年輕代中Eden區與Survivor區的大小比值。設定為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區佔整個年輕代的1/6
- -XX:MaxTenuringThreshold=0 設定垃圾最大年齡。如果設定為0的話,則年輕代物件不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件再年輕代的存活時間,增加在年輕代即被回收的概率