linux查看tomcat進程,linux進程阻塞的原因,釋放大塊內存時的阻塞問題

 2023-11-19 阅读 22 评论 0

摘要:一、前言在堆上申請和釋放內存的性能不高,這應該是常識了,尤其釋放大塊內存時,耗時更長,甚至會阻塞其他線程。做性能優化時,一般會采用內存池等手段避免頻繁的申請和釋放內存。本文從內核的角度分析申請和釋放內存時的阻塞瓶頸,及化

一、前言

在堆上申請和釋放內存的性能不高,這應該是常識了,尤其釋放大塊內存時,耗時更長,甚至會阻塞其他線程。做性能優化時,一般會采用內存池等手段避免頻繁的申請和釋放內存。本文從內核的角度分析申請和釋放內存時的阻塞瓶頸,及化解方法。

為了便于理解,本文從用戶申請、訪問、釋放內存的角度出發,逐步深入探討Linux的內存管理。本文以阻塞為線索,從堆內存和棧內存的區別,到malloc的原理,再到內存頁的管理,逐步找出釋放大塊內存時阻塞的原因,然后再給出一種化解的方法。

限于篇幅,沒有面面俱到的闡述,只介紹了和本文主旨相關的關鍵點。能力和時間有限,難免有紕漏甚至錯誤,歡迎指正。

二、堆和棧

3ffe9f44e9f2667ca05149796613f93f.png

2.1 棧內存

棧內存是線程預留的固定大小的內存空間,只需要移動棧頂指針就可以完成申請和釋放內存,因此速度很快,但大小受限。

2.2 堆內存

堆內存是程序運行時動態申請的,所以需要考慮競爭、碎片等問題,所以申請和釋放的速度慢一些,但是更靈活。本文主要講述使用堆內存的場景。

三、malloc

在c語言中,堆內存一般是用malloc函數申請的。malloc負責向系統申請內存和維護緩存(bins)。malloc通過brk/sbrk或mmap向系統申請內存,區別在于前者是通過增長堆地址空間擴大內存區域,而后者開辟新的內存區域,如下圖。不過在內核看來它們并沒有本質的區別。大塊內存的申請和釋放都是由mmap/munmap來完成的,所以“釋放大塊內存時的阻塞問題”也是由munmap造成的。測試發現munmap一塊20GB的內存,會阻塞其他線程的malloc(brk/sbrk/mmap)1秒左右。

faa1227b550b170d0b80e74f27b31884.png

四、Linux 內存管理

為了弄清楚munmap阻塞的原因,先要了解一些Linux的內存管理機制。

4.1 頁表(Page Table)

CPU 看到的內存地址是虛擬地址,頁表中存儲著虛擬地址到物理地址的映射關系,每次訪問內存時由 MMU 完成虛擬地址到物理的轉換。為了給這個轉換過程加速又引入了 TLB,TLB 可以理解為虛擬地址到物理地址映射的 cache,它速度很快但容量較小。這里關鍵的一點是:如果操作系統更改了頁表內容,它必須相應的刷新TLB以使CPU不誤用過時的表項。

4.2 虛擬地址空間(vm_area)

前面講到 mmap 可以申請一段新的虛擬內存區間,也就是說進程的虛擬地址空間不一定是連續的。所以內核使用鏈表來表示整個進程的地址空間,地址空間相接的兩個節點可以合并,一個節點也可以因為狀態變化而分割成多個。

4.3 缺頁中斷(Page Fault)

使用malloc(brk/sbrk/mmap)申請內存時,系統只分配了虛擬地址空間,并未分別物理地址,也就是說沒有對應的頁表項。當第一次訪問該區域時,因為沒有對應的頁表,所以MMU會產生一個缺頁中斷給CPU,CPU再根據虛擬地址空間的描述申請物理內存,補充頁表,然后再重新執行訪問該內存區域的指令。

4.4 頁框回收算法(PFRA)

內核會將不活躍的內存收回給其他進程使用。PFRA算法比較復雜,大致的思路是遍歷內存,找到可以回收且近期未使用(LRU)的內存進行回收。

4.4.1 哪些內存可以回收

頁類型

說明

回收操作

不可回收頁

1. 空閑頁(包含子伙伴系統列表中)

2. 保留頁(PG_reserved標志置位)

3. 內核動態分配頁4. 進程內核態堆棧頁

5. 臨時鎖定頁(PG_locked標志置位)

6. 內存鎖定頁(在先行區中且VM_LOCKED標志置位)

不允許也無需回收

可回收頁

1. 用戶太地址空間的匿名頁

2. tmpfs文件系統的映射頁(如IPC共享內存的頁)

將頁的內容保存在交換區

可同步頁

1. 用戶態地址空間的映射頁

2. 存有磁盤文件數據且在頁高速緩存中的頁

3. 塊設備緩沖區頁

4. 某些磁盤高速緩存的頁(如索引節點高速緩存)

必要時,與磁盤鏡像同步這些頁

可丟棄頁

1. 內存高速緩存中的未使用頁(如slab分配器高速緩存)

2. 目錄想高速緩存的未使用頁

無需操作

簡單的說就是大部分內核占用的內存不可以回收,大部分用戶進程占用的內存都可以回收,被mlock標記的內存不可以回收。

4.4.2 LRU

為了能快速的找到不活躍的內存,Linux 使用了 LRU 鏈表。這里有個巧妙的設計。硬件只能標記哪些內存被訪問過,只有一個標記位,沒有訪問時間的標記。所以Linux用3類(5個)FIFO的鏈表存儲內存頁,分別是活躍的內存鏈表、不活躍的內存鏈表、不可回收的內存鏈表。內核負責在這個三個鏈表間調度。

4.4.3 mlock

上面提到,被mlock(系統調用)標記的內存不可以被回收。那mlock是如何工作的呢?mlock在進程的虛擬地址空間(vm_area)做標記,前面提到vm_area是一個鏈表,所以mlock可能需要拆分或者合并vm_area節點。這個操作并不復雜,麻煩的是需要把被鎖定的內存頁移動到不可回收的內存鏈表(LRU),這個時間復雜度是線性的,也就是說操作的內存越多時間越長。

4.4.4 madvise

除了內核回收內存,用戶也是可以主動回收指定內存的,通過madvise(MADV_DONTNEED)實現。和free不同的是,madvise(MADV_DONTNEED)只釋放了頁表,并沒有釋放虛擬地址空間(vm_area),也就是說被madvise(MADV_DONTNEED)回收的內存還可以再訪問,只不過要重新出發缺頁中斷來分配物理內存。

五、阻塞的原因(Lock)

頁表、虛擬地址空間、LRU各有一把鎖,malloc(brk/sbrk/mmap)、free(munmap)、madvise、mlock和缺頁中斷都有可能獲取其中的一個或多個鎖。在操作大塊內存時,由于內存頁較多,處理的時間較長,就會出現阻塞其他線程的現象。

5.1 malloc(brk/mmap)

申請內存時只需要修改虛擬地址空間,在鏈表中做插入、合并等操作,所以相對輕量一些。

5.2 缺頁中斷

第一次訪問內存時觸發缺頁中斷,只要不是集中的觸發,一般不會出現明顯的阻塞。

5.3 mlock

修改虛擬地址空間的狀態,可能會觸發節點的分割、合并等操作

修改LRU鏈表

填充頁表

mlock的處理時間和內存頁的個數是線性關系,所以操作大塊內存時會發生明顯的阻塞。

5.4 madvise

madvise可以清除頁表項,處理的內存頁越多時間越長。

5.5 free(munmap)

釋放內存比申請內存麻煩的多,需要釋放虛擬地址空間、清除頁表項、修改LRU鏈表。所以釋放大塊內存時會出現明顯的阻塞現象。

d91f63ff2369a729bcf5a07618b9eaf2.png

六、緩解阻塞的方法

6.1 分批操作

free/munmap 前用madivse分批的釋放頁表

mlock/munlock 也要分批執行

malloc/mmap 后可以分批的

6.2 大頁(Huge Page)

Linux默認使用的頁是4k,大頁內存可以使用2M甚至更大的頁,能有效減少頁的數量,從提供大塊內存使用的效率。

七、測試數據

多次測試結果有一定差異,但基本不會超過一個數量級。

序號

內存大小

方法

最大阻塞時長

備注

1

20GB

free

1476 ms

未加特殊處理

2

20GB

munmap

704 ms

內存映射

3

20GB

madvise + munmap

0.3 ms

先用madvise分批釋放頁表項,再用munmap卸載內存

4

20GB

mlock + munmap

4667 ms

沒有做分批處理

5

20GB

mlock + madvise + munmap

3.5 ms

分段的mlock、munlock、madvise,可以明顯的緩解阻塞的問題

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/5/182853.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息