Devin.KR

CS · 기본

개발자를 위한 컴퓨터 사이언스 - 내 코드가 실제로 도는 곳

가상 메모리 - 페이지와 주소 변환, RSS와 스왑, 메모리 부족 판단 (CS 기초 7장)

가상 주소와 물리 페이지의 대응, 페이지 폴트와 스왑을 작은 모형으로 살펴보고, RSS와 VSZ의 차이 및 호스트와 컨테이너의 메모리 부족을 구분하는 절차를 익힌다.

개발자 · 원고 갱신

이 장에서 배우는 것

6장에서는 CPU를 사용할 준비와 실제 배정이 다르다는 점을 확인했다. 이번에는 메모리를 사용할 주소를 확보한 일과 물리 메모리에 데이터가 머무는 일을 나눈다. 선수지식은 프로세스의 주소 공간과 메모리 계층이다. 주소 공간의 크기를 실제 RAM 소비량으로 읽는 오해를 먼저 풀어야 장애 지표를 해석할 수 있다.

  • 가상 주소를 페이지 번호와 내부 위치로 나누어 대응 관계를 설명한다.
  • 페이지 폴트, 상주 메모리, 스왑을 서로 다른 개념으로 구분한다.
  • 메모리 부족을 호스트와 컨테이너의 범위로 나누어 종료 원인을 확인한다.

개념

큰 파일을 다루는 프로그램의 가상 메모리가 커졌지만 실제 상주 메모리는 거의 늘지 않았다고 가정한다. 주소가 많이 잡혔다는 이유만으로 누수를 선언하면 정상적인 예약과 매핑을 오류로 볼 수 있다. 반대로 호스트에 여유 RAM이 보인다는 이유로 컨테이너 종료를 메모리 문제에서 제외할 수도 없다. 메모리 지표는 무엇을 어디에서 세었는지 붙여야 의미가 완성된다.

주소는 직접적인 RAM 위치가 아니다

프로세스가 사용하는 가상 주소는 주소 변환을 거쳐 물리 메모리의 위치와 연결된다. 페이지는 그 대응을 관리하는 단위이며, 하나의 주소는 페이지 번호와 페이지 안의 위치로 나눌 수 있다. 페이지 크기는 환경에서 확인할 값이지 모든 컴퓨터에서 항상 같은 상수가 아니다. 다음 예제의 4096은 계산을 쉽게 보이도록 정한 교육용 모형의 값이다.

가상 주소를 페이지 번호와 오프셋으로 나누고 페이지 표에서 물리 페이지를 찾아 같은 오프셋을 더한다. 없는 대응은 페이지 폴트 처리로 이어진다.

가상 주소를 페이지 번호와 오프셋으로 나누고 페이지 표에서 물리 페이지를 찾아 같은 오프셋을 더한다. 없는 대응은 페이지 폴트 처리로 이어진다. 그림의 경계는 소유 범위를, 화살표는 실행 또는 참조 방향을 뜻한다.

주소 공간을 나누면 프로세스마다 독립된 배치를 제공하고 접근 권한을 확인할 수 있다. 서로 다른 가상 주소가 공유되는 같은 물리 페이지를 가리킬 수도 있다. 반대로 같은 숫자의 가상 주소라도 프로세스가 다르면 다른 내용일 수 있다. 실제 주소 변환은 여러 단계의 표와 변환 캐시 등을 활용하지만 이 장에서는 대응 관계를 읽는 데 필요한 범위만 다룬다.

개념세는 대상바로 결론 내릴 수 없는 것
VSZ·가상 크기가상 주소 공간의 크기같은 양의 RAM을 독점함
RSS·상주 크기현재 RAM에 상주한 프로세스 페이지전부 이 프로세스만의 비용임
스왑 사용보조 저장 영역으로 옮겨진 메모리지금도 계속 심하게 교환 중임
사용 가능 메모리새 작업에 제공 가능한 양의 추정모든 제한 범위에서 할당 가능함

페이지 폴트는 반드시 오류가 아니다

명령이 어떤 주소를 접근했을 때 현재의 변환 상태로 곧바로 진행할 수 없으면 페이지 폴트 처리가 필요할 수 있다. 정상적인 첫 접근, 쓰기 시 복사, 저장된 내용을 가져오는 과정도 여기에 포함될 수 있다. 적법한 접근인지 확인한 뒤 운영체제가 필요한 조치를 하고 명령을 재개할 수 있다. 접근 권한이나 주소 자체가 잘못되면 정상적으로 복구하지 못할 수도 있으므로 두 경우를 구분한다.

마이너 폴트는 일반적으로 처리를 위해 해당 페이지의 저장장치 읽기가 필요하지 않은 경우이고, 메이저 폴트는 그런 읽기가 필요한 경우를 가리킨다. 메이저 폴트가 보인다고 모두 스왑을 읽었다고 판단해서는 안 된다. 파일에 대응된 페이지를 읽는 상황도 생각해야 한다. 횟수만으로 지연 시간을 정할 수도 없으므로 접근 대상과 응답 시간의 변화를 함께 본다.

큰 주소 범위를 먼저 예약하고 나중에 일부만 접근하는 프로그램은 VSZ와 RSS가 크게 다를 수 있다. 사용하지 않는 페이지에 실물 데이터를 배치할 필요가 아직 없기 때문이다. 작은 반복 실험에서 일부 주소를 실제로 만진 전후를 비교하면 이런 차이를 이해하기 쉽다. 다만 운영 서비스에서 큰 배열을 무작정 만들어 한계까지 채우는 실험은 진단에 필요하지 않다.

회수와 부족은 제한 범위 안에서 판단한다

커널은 메모리 압박이 생겼을 때 다시 읽을 수 있는 캐시 페이지를 회수하거나, 설정과 조건에 따라 일부 내용을 스왑으로 내보낼 수 있다. 깨끗한 파일 캐시의 회수와 익명 메모리의 스왑은 같은 작업이 아니다. 이미 스왑 사용량이 있다는 사실만으로 현재 활발한 교환이 계속된다고 말할 수 없다. 시간에 따른 입출력 변화와 작업 지연을 함께 보아야 한다.

메모리 확보가 어려워지면 OOM 처리에서 프로세스가 종료될 수 있다. 이때 가장 큰 RSS 하나만 보고 종료 대상이 정해진다고 단순화하지 않는다. 대상 범위, 회수 가능성, 프로세스별 조정값 등 정책과 조건이 개입한다. 시스템 전체의 부족과 cgroup 제한 안에서의 부족은 범위가 다르므로 호스트 메모리 그래프 하나가 두 경우를 모두 설명하지 못한다.

호스트 여유 있음 + 그룹 한도 도달 → 그룹 내부 부족 가능
VSZ 증가 + RSS 안정 → 예약·매핑 등 후보 확인
RSS 증가 + 종료 기록 없음 → 누수 확정 불가

직접 확인하기

주소 변환과 상주량을 따로 계산한다

다음 Python 3 예제는 메모리를 실제 한계까지 할당하지 않는 안전한 모형이다. 페이지 표에 가상 페이지 1이 물리 페이지 4와 연결되어 있다고 정했다. 주소 5000을 나누면 페이지 번호는 1이고 안쪽 위치는 904다. 이 위치가 변환 뒤에도 유지된다는 점을 숫자로 확인한다.

page_size = 4096  # educational model, not an OS query
page_table = {0: 9, 1: 4}
address = 5000
page, offset = divmod(address, page_size)
physical = page_table[page] * page_size + offset
print("virtual page", page, "offset", offset)
print("physical address", physical)
결과
----
virtual page 1 offset 904
physical address 17288

페이지 표에 대응이 없다고 언제나 프로그램이 잘못된 것은 아니다. 운영체제가 합법적인 매핑을 준비하는 경우와 허용되지 않은 접근을 거절하는 경우를 더 조사해야 한다. 위 모형은 대응이 있는 경로만 실행한다. 따라서 계산 결과를 실제 Python 객체의 물리 주소라고 소개하지 않는다.

두 번째 모형은 주소 공간에 백 페이지가 있어도 상주한 페이지 수는 더 작을 수 있음을 보인다. 새로운 페이지를 건드린 상황을 집합에 번호 하나를 넣는 방식으로 표현했다. 실제 운영체제가 매 접근마다 정확히 한 페이지씩 상주량을 늘린다는 보장은 아니다. 사전 읽기, 공유와 회수 같은 조건은 단순 모형 밖에 있다.

virtual_pages = 100
resident = {0, 1, 2, 3}
print("virtual pages", virtual_pages)
print("resident before", len(resident))
resident.add(80)
print("resident after touch model", len(resident))
결과
----
virtual pages 100
resident before 4
resident after touch model 5

공유 페이지는 합산할 때 다시 센다

두 프로세스의 RSS를 더하면 같은 공유 페이지가 두 번 포함될 수 있다. 아래 모형에서는 각 프로세스가 세 페이지를 참조하지만 실제 서로 다른 페이지는 네 개뿐이다. 상주량의 합과 물리 메모리 전체 사용량을 동일시하면 이런 중복을 놓친다. 더 세밀한 분석에는 공유를 비례 배분하는 지표나 매핑별 정보를 검토할 수 있다.

a = {"private-a", "shared-1", "shared-2"}
b = {"private-b", "shared-1", "shared-2"}
print("RSS sum in pages", len(a) + len(b))
print("unique resident pages", len(a | b))
print("shared pages", len(a & b))
결과
----
RSS sum in pages 6
unique resident pages 4
shared pages 2

마지막 코드는 실행 중인 자신의 Linux 상태 파일에서 세 항목만 읽는다. Linux 이외에서는 명시적으로 건너뛴다. 아래 검증 결과는 이 원고를 작성한 macOS 환경의 결과이므로 Linux 측정에 성공했다고 주장하지 않는다. Linux에서 실행하면 순간 값이 달라지며 결과를 남길 때 운영체제, CPU, 관찰 시점도 함께 기록해야 한다.

from pathlib import Path
status = Path("/proc/self/status")
if status.exists():
    wanted = {"VmSize", "VmRSS", "VmSwap"}
    for line in status.read_text().splitlines():
        if line.split(":", 1)[0] in wanted:
            print(line)
else:
    print("SKIP: Linux /proc is unavailable")
결과
----
SKIP: Linux /proc is unavailable

환경별 차이

환경판단에 필요한 정보주의할 점
Linux상주량·매핑·회수 및 종료 기록/proc의 일부 통계는 정밀한 순간 스냅샷이 아님
macOS해당 도구의 메모리·압축·압박 정의Linux 필드 이름과 단위로 치환하지 않음
cgroup v2memory.current·memory.max·memory.events호스트 여유와 그룹 한도는 별개
스왑 없는 환경회수 가능량과 메모리 제한스왑 증가가 없다고 부족을 배제하지 않음

Linux의 MemAvailable은 단순히 비어 있는 메모리만을 뜻하지 않는다. 회수 가능성을 고려하여 새 작업에 제공할 수 있는 양을 추정하므로 MemFree와 구별한다. 캐시를 쓰는 시스템에서 빈 메모리가 적다는 것은 흔한 모습이다. 그러나 추정치가 크다고 모든 크기의 할당이나 모든 컨테이너의 작업 성공을 보장하는 것도 아니다.

cgroup v2에서 memory.current는 해당 그룹과 하위 그룹의 현재 사용을 파악하는 출발점이고 memory.max는 상한 판단의 기준이다. memory.events의 oom과 oom_kill 등은 부족 발생과 종료를 구별해 읽는 데 도움이 된다. 누적값은 직전 기록과의 차이를 확인하며 상위 제한도 함께 조사한다. 종료된 뒤의 낮은 메모리 값만 보면 종료 직전의 피크가 사라져 원인을 놓칠 수 있다.

종료 코드만으로 OOM이라고 확정하지 않는다. 강제 종료에는 다른 원인도 있으므로 플랫폼의 종료 사유, 커널 기록, 제한 이벤트가 서로 맞는지 확인한다. 반대로 응용 프로그램이 남긴 메모리 통계가 정상이어도 전체 프로세스와 그룹의 비용이 더 클 수 있다. 언어 내부 관리 영역과 운영체제가 세는 범위의 차이가 이 장의 진단 경계다.

실무에서 자주 틀리는 것

1. VSZ가 커졌으니 같은 양의 RAM을 샌다고 본다

가상 크기가 크게 늘어난 증상에서 곧바로 물리 메모리 누수라는 오진이 나온다. 실제 원인에는 예약된 주소 공간과 파일 매핑처럼 당장 전부 상주하지 않는 영역이 있다. 매핑 종류와 RSS의 추이를 함께 확인하고 입력량이 안정된 뒤에도 증가가 지속되는지 본다. 누수는 수명의 문제이므로 숫자의 크기만으로 결론 내리지 않는다.

2. free가 작다는 이유로 캐시를 문제 삼는다

비어 있는 메모리가 작지만 서비스 지연은 안정적인 증상이다. 흔한 오진은 캐시가 RAM을 빼앗아 반드시 장애 직전이라는 것이다. 실제로는 재사용을 위한 캐시가 공간을 활용하며 필요 시 일부를 회수할 수 있다. 사용 가능량 추정, 회수 활동, 폴트와 지연을 함께 확인하면 단순 활용과 압박을 구별할 수 있다.

3. 호스트가 여유로우니 OOM일 수 없다고 본다

호스트에 RAM이 남았는데 특정 컨테이너만 종료되는 증상이다. 흔한 오진은 메모리와 무관한 응용 프로그램 오류라는 것이다. 실제로는 해당 그룹이나 상위 그룹의 제한이 먼저 작동했을 수 있다. 그룹 사용 피크, 한도, 종료 이벤트와 시각을 맞추어야 제한 범위 안의 부족을 검증할 수 있다.

메모리 지표에는 소유 범위와 생명주기를 붙인다. 공유된 페이지는 중복 집계될 수 있고 일시적 피크는 종료 후 사라질 수 있다. 느린 추세와 순간 급증을 함께 기록해야 작은 평균값 뒤에 숨은 압박도 보인다. 이 기록은 한도를 올릴지, 동시성을 줄일지, 객체의 수명을 조사할지 선택하는 근거가 된다.

스스로 확인하기

  1. 모형의 페이지 크기가 4096이고 가상 주소가 5000이다. 페이지 번호와 오프셋을 구하고, 물리 페이지가 4일 때 최종 주소를 계산하라.
  2. 두 프로세스가 각각 세 페이지를 상주시키며 그중 두 페이지가 공유된다. RSS 합과 서로 다른 물리 페이지 수를 비교하라.
  3. 호스트는 여유롭지만 제한된 그룹이 종료되었다. OOM 여부를 확인할 자료 세 종류와 종료 뒤 현재값만 보는 문제를 설명하라.

첫 답은 페이지 1, 오프셋 904, 물리 주소 17288이다. 두 번째는 RSS 합 여섯 페이지와 서로 다른 네 페이지다. 세 번째는 그룹과 상위 한도, 사용 피크, 부족·종료 이벤트 또는 커널 기록을 확인한다. 종료 후 메모리가 해제되면 현재값이 낮아지므로 그 값만으로 종료 직전 부족을 배제할 수 없다.

참고와 출처: Linux /proc 문서, Linux cgroup v2 문서, Linux man-pages 자원 사용 통계를 사실 확인에 사용했다. 사례, 모형, 설명과 도식은 독립 작성했으며 원문이나 소스 코드를 발췌하지 않았다.

다음 8장에서는 메모리를 함께 볼 수 있는 실행 흐름들이 값을 고칠 때 생기는 문제를 살핀다. 같은 주소를 공유한다는 사실만으로 올바른 결과가 보장되지 않는 이유를 실행 순서로 확인한다.

READER FEEDBACK

질문·오탈자·의견

내용에 관한 질문이나 오탈자, 더 나은 설명을 위한 의견을 남겨 주세요. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.

댓글 0

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

댓글을 남기려면 로그인이 필요합니다.