I/O, Lưu trữ & Tài nguyên/RSS, VSZ — một tiến trình thật sự chiếm bao nhiêu RAM
21/26
Bài 21 / 26~13 phútTài nguyên & Đo lườngMiễn phí lượt xem

RSS, VSZ — một tiến trình thật sự chiếm bao nhiêu RAM

Vì sao tổng RSS của mọi tiến trình lớn hơn RAM thật mà máy vẫn chạy: trang chia sẻ, thư viện dùng chung, copy-on-write. VSZ, RSS và PSS đo cái gì khác nhau.

TL;DR: VSZ đếm toàn bộ không gian địa chỉ ảo mà tiến trình đã đăng ký, kể cả phần chưa hề chạm vào, nên nó gần như vô dụng khi hỏi "tốn bao nhiêu RAM". RSS đếm số trang đang thật sự nằm trong RAM, sát hơn nhiều nhưng có một khiếm khuyết chí mạng: trang dùng chung được tính đủ cho mọi tiến trình dùng nó, nên cộng RSS toàn máy thì ra con số lớn hơn cả RAM vật lý mà máy vẫn chạy êm. PSS sửa đúng chỗ đó bằng cách chia trang chia sẻ theo số bên dùng, và là chỉ số duy nhất trong ba cái này cộng lại được.

Một máy 16 GB RAM. Bạn chạy ps aux, cộng cột RSS của mọi tiến trình lại, và ra 31 GB. Máy không swap, không chậm, không có gì bất thường trong dmesg.

Không có phép màu nào ở đây, cũng chẳng phải công cụ báo sai. Chỉ là phép cộng đó không có nghĩa, và vì sao nó không có nghĩa là toàn bộ nội dung bài này.

1. Analogy — chung cư và bức tường chung

Một toà chung cư 1.000 m². Hỏi mười hộ "căn của bạn rộng bao nhiêu", ai cũng tính cả bức tường ngăn với hàng xóm, cả cầu thang, cả hành lang. Cộng mười câu trả lời lại thì ra 1.400 m², to hơn cả toà nhà.

Không ai nói dối. Bức tường giữa hai căn có thật, và nó phục vụ cả hai. Cái sai nằm ở phép cộng: bạn vừa cộng mỗi bức tường hai lần.

Muốn con số cộng được thì phải đổi cách khai: tường chung giữa hai hộ thì mỗi hộ khai một nửa. Đó đúng là điều PSS làm với những trang bộ nhớ được nhiều tiến trình dùng chung.

Ở chung cưTrong hệ thống
Diện tích được cấp trên giấy tờ, kể cả phần chưa xâyVSZ — không gian địa chỉ ảo đã đăng ký
Diện tích sàn thật sự đang chiếm chỗRSS — trang đang nằm trong RAM
Tường chung khai đủ cho cả hai hộTrang chia sẻ bị tính trùng trong RSS
Tường chung chia đôi cho hai hộPSS — trang chia sẻ chia theo số bên dùng

Ba tien trinh dung chung mot thu vien, RSS dem tron con PSS chia deu phan chung

Nhớ lại Đọc top cho đúng

Bài trước đọc phần CPU trong header của top. Ba cột VIRT, RES, SHR nằm ngay bên dưới trong cùng màn hình đó chính là VSZ, RSS và phần chia sẻ mà bài này mổ.

2. VSZ và RSS đo hai thứ gì khác nhau?

Nguồn thật nằm ở file status trong thư mục /proc của tiến trình, và nó chi tiết hơn hẳn cột trong ps:

grep -E 'VmSize|VmPeak|VmRSS|VmHWM|RssAnon|RssFile|RssShmem' /proc/self/status
# VmPeak:   2724316 kB
# VmSize:   2724316 kB
# VmHWM:      52140 kB
# VmRSS:      51884 kB
# RssAnon:    18232 kB
# RssFile:    33652 kB
# RssShmem:       0 kB

Đọc cặp đầu tiên là thấy ngay khoảng cách: VmSize (chính là VSZ) 2,7 GB, còn VmRSS (chính là RSS) 51 MB. Tiến trình này đăng ký một không gian địa chỉ khổng lồ nhưng chỉ đụng vào một phần nhỏ xíu của nó.

VSZ đếm mọi vùng đã ánh xạ vào không gian địa chỉ ảo, bất kể trang đó có nằm trong RAM hay không, thậm chí bất kể nó đã từng được chạm tới hay chưa. Một JVM đặt -Xmx8g đăng ký trước cả vùng heap ngay lúc khởi động; một thư viện mmap một file 4 GB cũng cộng luôn 4 GB vào VSZ dù chưa đọc byte nào. Vì thế VSZ trả lời câu hỏi "tiến trình này có quyền chạm tới bao nhiêu địa chỉ", chứ không trả lời "nó đang tốn bao nhiêu RAM".

RSS đếm số trang thật sự đang nằm trong RAM vật lý tại thời điểm đo. Đây mới là chỉ số đáng nhìn — nhưng nó vẫn chưa phải câu trả lời, vì lý do ở mục sau.

Hai trường VmPeakVmHWM ghi lại đỉnh của VSZ và RSS trong suốt đời tiến trình. Một service giờ chỉ chiếm 400 MB nhưng VmHWM là 6 GB thì bạn vừa bắt được dấu vết của cú phình mà không ai kịp nhìn thấy lúc nó xảy ra.

Ba dòng RssAnon, RssFile, RssShmem chia RSS theo nguồn gốc, và cách chia này quay lại rất quan trọng ở bài 05:

TrườngLà trang gìKhi cần RAM thì kernel làm gì
RssAnonTrang ẩn danh: heap, stack, vùng cấp phát độngChỉ có thể đẩy sang swap; không swap thì không thu hồi được
RssFileTrang có file nền: mã lệnh, thư viện, file được ánh xạBỏ đi được ngay, cần thì đọc lại từ đĩa
RssShmemBộ nhớ chia sẻ và tmpfsSống theo vùng chia sẻ, không theo tiến trình

Đây là lý do hai tiến trình cùng RSS 2 GB có thể gây hai hậu quả hoàn toàn khác nhau lúc máy cạn RAM: cái nào toàn RssFile thì kernel dọn nhẹ nhàng, cái nào toàn RssAnon thì không có đường lùi.

3. Vì sao tổng RSS lớn hơn RAM thật?

một trang vật lý có thể nằm trong RSS của nhiều tiến trình cùng lúc, và mỗi tiến trình đều tính đủ nó.

Ba nguồn gây chồng lấn, xếp theo mức phổ biến:

Thứ nhất là thư viện dùng chung. Toàn máy chỉ có một bản libc trong RAM, nhưng mọi tiến trình đều ánh xạ nó vào không gian của mình và đều tính phần đang cư trú vào RSS của mình. Hai trăm tiến trình dùng chung một thư viện 100 MB thì phép cộng ra 20 GB cho thứ chiếm đúng 100 MB.

Thứ hai là fork với copy-on-write. Ngay sau fork, tiến trình con chia sẻ toàn bộ trang với cha; kernel chỉ nhân bản một trang khi có bên ghi vào nó. Một tiến trình 8 GB fork ra bốn tiến trình con thì ps báo 40 GB, còn RAM thật sự tốn thêm gần bằng không cho tới lúc có ai ghi.

Thứ ba là file được ánh xạ: trang page cache của một file đang được nhiều tiến trình mmap cũng vào RSS của từng bên.

Hệ quả cho việc giám sát

Alert dạng "tổng RSS vượt 90% RAM" gần như chắc chắn báo động giả trên máy chạy nhiều bản sao của cùng một service. Ngưỡng đáng đặt nằm ở dung lượng còn dư thật, mức swap đang dùng, và trong container là memory.current so với memory.maxbài 04.

4. PSS — chỉ số duy nhất cộng lại được

Kernel bày sẵn cách đếm không trùng, trong smaps: mỗi vùng ánh xạ được kê riêng, và trang chia sẻ được chia theo tỉ lệ số tiến trình đang dùng. Một trang 4 KB được 4 tiến trình dùng chung thì mỗi bên nhận đúng 1 KB vào PSS của mình.

# Tong PSS cua mot tien trinh, tinh bang MB
awk '/^Pss:/ { total += $2 } END { print total/1024, "MB" }' /proc/self/smaps

Cùng file đó còn tách phần riêng ra khỏi phần chung: Private_Clean là trang chỉ mình tiến trình này dùng và giống hệt bản trên đĩa, Private_Dirty là trang chỉ mình nó dùng và đã bị sửa. Cặp này trả lời trực tiếp câu hỏi thực dụng nhất khi máy hết RAM: giết tiến trình đó thì thu lại được bao nhiêu? Đáp án là phần riêng của nó, chứ không phải RSS, vì trang chia sẻ vẫn còn bên khác dùng nên không đi đâu cả.

Cạnh smaps còn có một file tổng hợp sẵn để script giám sát khỏi phải cộng tay từng vùng.

Ba chỉ số, ba câu hỏi khác nhau:

Câu hỏiChỉ số đúng
Tiến trình này có quyền chạm bao nhiêu địa chỉ?VSZ
Nó đang giữ bao nhiêu trang trong RAM?RSS
Nó chịu trách nhiệm cho bao nhiêu RAM, tính công bằng?PSS
Giết nó thì máy thu lại bao nhiêu?Tổng phần riêng

5. Pitfall của riêng concept này

Nhầm 1 — cộng RSS toàn máy rồi hoảng.

✅ Phép cộng đó đếm trùng mọi trang chia sẻ. Muốn cộng thì cộng PSS. Còn muốn biết máy có sắp hết RAM không thì nhìn dung lượng khả dụng và mức swap, chứ không nhìn tổng của một cột.

Nhầm 2 — dùng VSZ để đặt hạn mức bộ nhớ.

✅ VSZ phồng lên vì đủ thứ không tốn RAM: vùng đăng ký trước, file ánh xạ, arena của trình cấp phát. Đặt trần theo VSZ thì hoặc bạn giết oan một tiến trình khoẻ mạnh, hoặc bạn đặt một con số to tới mức chẳng chặn được gì.

Nhầm 3 — thấy RSS của tiến trình con bằng RSS của cha rồi kết luận bộ nhớ nhân đôi sau fork.

✅ Ngay sau fork thì hai bên dùng chung gần như toàn bộ trang nhờ copy-on-write. RAM thật chỉ tăng dần theo lượng trang bị ghi vào. Muốn thấy con số thật thì nhìn PSS của hai tiến trình.

Nhầm 4 — coi RSS thấp là bằng chứng không rò rỉ bộ nhớ.

✅ Rò rỉ có thể nằm ở phần đã bị đẩy sang swap, hoặc ở bộ nhớ ngoài heap mà công cụ theo dõi heap không thấy. So VmHWM với RSS hiện tại, và nhìn xu hướng thay vì một lần đo.

6. 📚 Đào sâu (tuỳ chọn)

📚 Nguồn kỹ thuật

Đọc smaps của một tiến trình đang chạy trên máy bạn là bài tập bổ ích bất ngờ: tỉ lệ giữa phần riêng với phần chung thường lệch xa dự đoán.

7. Liên hệ các bài khác

  • top, htop và load average — ba cột VIRT, RES, SHR trong cùng màn hình đó là ba chỉ số bài này vừa mổ.
  • cgroups — trần tài nguyên — trần bộ nhớ của container đếm theo cách khác hẳn RSS, và đó là lý do container bị giết trong khi top trông vẫn ổn.
  • OOM kill và ulimit — dòng log OOM in ra anon-rss với file-rss riêng; chia được hai loại đó thì đọc được vì sao đúng tiến trình ấy bị chọn.
  • mmap và copy-on-write — cơ chế tạo ra chuyện chồng lấn mà bài này đang đếm hậu quả.

8. Tóm tắt

  • Khoảng cách giữa VmSizeVmRSS trong file status thường là vài chục lần, và nó không phải dấu hiệu bất thường.
  • VmHWM giữ lại đỉnh RSS sau khi cú phình đã qua — thứ đáng xem đầu tiên khi khám nghiệm một sự cố bộ nhớ.
  • Chia RSS thành RssAnon với RssFile cho biết kernel còn đường lùi hay không: trang có file nền bỏ đi được, trang ẩn danh thì không.
  • Trang chia sẻ khiến RSS không cộng được; PSS chia chúng theo số bên dùng nên tổng PSS mới có nghĩa.
  • Câu hỏi "giết tiến trình này thu lại bao nhiêu RAM" trả lời bằng phần riêng, không bằng RSS.

9. Tự kiểm tra

Tự kiểm tra
Q1
Trên máy 16 GB, tổng cột RSS của mọi tiến trình là 31 GB nhưng máy không swap và chạy bình thường. Giải thích, và nêu con số nào mới đáng đặt cảnh báo.

Một trang vật lý nằm trong RSS của mọi tiến trình đang ánh xạ nó, nên phép cộng đếm trùng mọi thứ dùng chung: thư viện, mã lệnh, file được ánh xạ, và trang copy-on-write còn nguyên sau fork. Máy chạy nhiều bản sao của cùng một service thì mức đếm trùng càng lớn.

Cảnh báo nên đặt trên dung lượng còn khả dụng thật của máy và mức swap đang dùng; nếu bắt buộc phải tính theo tiến trình thì dùng tổng PSS, vì đó là chỉ số duy nhất trong ba cái cộng lại có nghĩa.

Q2
Một JVM báo VSZ 12 GB nhưng RSS chỉ 700 MB. Đồng nghiệp muốn giảm VSZ vì sợ hết RAM. Vì sao lo lắng đó đặt sai chỗ?

VSZ đếm mọi vùng đã ánh xạ vào không gian địa chỉ ảo, kể cả vùng đăng ký trước mà chưa hề chạm tới. JVM đăng ký sẵn không gian cho heap tối đa, cho metaspace và cho arena của trình cấp phát ngay lúc khởi động, nên con số 12 GB nói về địa chỉ chứ không nói về RAM.

RAM thật đang tốn là 700 MB, và đó mới là con số đối chiếu với dung lượng máy. Trên máy 64-bit, không gian địa chỉ gần như miễn phí; siết VSZ chỉ làm tiến trình chết sớm vì không đăng ký nổi vùng nó cần.

Q3
Hai tiến trình đều có RSS 2 GB. Một cái gần như toàn RssFile, cái kia gần như toàn RssAnon. Khi máy cạn RAM, số phận hai tiến trình khác nhau thế nào và vì sao?

Trang có file nền (RssFile) là bản sao của thứ đã nằm trên đĩa, nên kernel thu hồi được ngay mà không cần ghi gì; lúc nào cần lại thì đọc lại từ file. Áp lực bộ nhớ vì thế được giải toả gần như tức thì, cái giá là những lần đọc lại sau đó.

Trang ẩn danh (RssAnon) là heap và stack, không có bản nào trên đĩa. Kernel chỉ có hai lựa chọn: đẩy sang swap, hoặc nếu máy không bật swap thì không thu hồi được gì cả. Tiến trình thứ hai vì thế vừa là nguồn gây áp lực, vừa là ứng viên nặng ký cho OOM killer.

Q4
Bạn cần chọn giữa hai tiến trình để dừng bớt cho máy nhẹ đi. Cái A có RSS 3 GB, cái B có RSS 2 GB. Chỉ số nào mới quyết định được, và vì sao RSS có thể dẫn bạn chọn sai?

Chỉ số quyết định là phần riêng của mỗi tiến trình, tức tổng Private_Clean cộng Private_Dirty trong smaps. Giết một tiến trình chỉ trả lại những trang không còn ai dùng; trang chia sẻ vẫn ở nguyên đó phục vụ các tiến trình khác.

RSS dẫn sai vì nó tính trọn phần chung. Tiến trình A hoàn toàn có thể có 3 GB gần như toàn thư viện dùng chung với hai chục tiến trình khác, nên giết nó thu về vài trăm MB; trong khi B có 2 GB heap riêng, giết nó là trả lại đủ 2 GB.

Q5
Ngay sau khi một service 8 GB gọi fork bốn lần, ps báo tổng RSS 40 GB trên máy 16 GB. Chuyện gì thật sự xảy ra trong RAM, và khi nào con số đó mới trở thành nguy hiểm thật?

Copy-on-write khiến cha và bốn con dùng chung gần như toàn bộ trang ngay sau fork: kernel chỉ đánh dấu các trang là chỉ-đọc và chưa nhân bản gì cả. RAM thật tốn thêm gần bằng không, còn ps thì tính đủ 8 GB cho từng tiến trình nên ra 40 GB.

Nguy hiểm bắt đầu khi các bên ghi vào những trang đó: mỗi lần ghi buộc kernel nhân bản một trang riêng, và phần chung teo dần thành phần riêng. Mức tiêu thụ vì thế bò lên theo tỉ lệ trang bị chạm, không có mốc nào rõ ràng để cảnh báo, nên thứ đáng theo dõi là PSS chứ không phải RSS.

Bài tiếp theo: Đo I/O bằng iostat — và vì sao %util nói dối

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Đo I/O bằng iostat — và vì sao %util nói dối