가상 메모리(Virtual Memory)는 운영 체제가 물리적 메모리(램)의 제한을 극복하고, 프로그램 실행에 필요한 메모리를 효과적으로 관리하기 위해 사용하는 메모리 관리 기법.
주요 개념
가상 주소와 물리 주소 : 가상 메모리는 프로그램이 메모리를 가상 주소로 참조하도록 합니다. 운영 체제는 가상 주소를 실제 물리적 메모리 주소로 변환하여 프로그램을 실행.(mapping)
페이지(paging) : 가상 메모리는 일정 크기의 페이지(page) 단위로 나눔. 프로그램이 필요로 하는 데이터만 물리 메모리에 적재하여 효율적으로 사용
swapping : 물리적 메모리가 부족할 경우, 사용하지 않는 페이지를 디스크의 스왑 영역으로 이동시켜 공간을 확보. 다시 필요할 때 디스크에서 가져와 메모리에 적재.
: swapping의 과정 - 여러 프로그램이 동시에 실행되거나 프로그램이 사용하는 데이터 크기가 물리적 메모리 크기를 초과하면 메모리 부족 -> 운영 체제는 메모리 내에서 당장 사용되지 않는 페잊(Idle Pages)를 식별하여 페이지 교체 알고리즘을 사용해 교체할 페이지를 선택. -> 선택된 페이지를 디스크의 스왑 영역에 저장. 스왑 영역이란 일반적으로 운영 체제에서 따로 설정한 디스크 공간. -> 스왑된 페이지가 차지한 물리적 메모리 공간을 해제하여 다른 데이터를 적재할 수 있도록 함. -> 프로그램이 스왑된 페이지를 다시 요청하면 페이지 폴트(Page Fault)가 발생. 운영 체제는 해당 페이지를 디스크에서 물리 메모리로 다시 가져와 적재. 필요 시, 또 다른 페이지를 스왑하여 공간을 확보.
: Page Fault란 프로세스가 참조하려는 페이지가 현재 물리적 메모리에 없는 상황.
가상 메모리의 특징
가상 메모리 작동 과정
프로그램이 가상 주소를 참조 -> 운영 체제는 mmu를 통해 가상 주소를 물리 주소로 변환하기 위해 페잊 테이블을 참조 -> 참조한 페이지가 물리 메모리에 있으면 페이지 히트하고 없으면 페이지 폴트 -> 페이지 폴트 발생시, 필요한 페이지를 디스크에서 읽어 물리 메모리에 적재. -> 물리 메모리가 가득 찼다면 사용되지 않는 페이지를 디스크로 스왑하여 공간을 만듬.
++ 물리 메모리는 RAM으로 프로그램 실행에 필요한 데이터를 저장하고 CPU가 직접 접근하여 빠르게 처리. 가상 메모리는 물리 메모리의 제한을 넘어서기 위해 사용하는 논리적 메모리 공간. 디스크는 영구적으로 저장하거나 메모리 부족시 사용되는 스왑 영역이 여기에 포함.
단, 디스크 접근(스왑)이 많아질 경우(즉, 페이지 폴트)가 많아질 경우 속도가 느려짐. 또한 페잊 테이블 관리와 변환 작업으로 추가적인 처리 비용. 페이지 폴트시에서 디스크에서 데이터를 읽어오는 데 시간이 오래걸림.
가상 메모리의 역할을 이해하기 위해 메모리 부족 상황에서 Java 프로그램이 어떻게 동작하는지 실험합니다.
대규모 배열 생성:
코드 예시:
import java.util.ArrayList;
public class MemoryTest {
public static void main(String[] args) {
ArrayList<byte[]> memoryHog = new ArrayList<>();
try {
while (true) {
// 10MB 크기의 배열을 계속 추가
memoryHog.add(new byte[10 * 1024 * 1024]);
System.out.println("Allocated 10MB more...");
}
} catch (OutOfMemoryError e) {
System.err.println("OutOfMemoryError occurred!");
}
}
}
실험 환경:
-Xmx64mJava의 Garbage Collection(GC)이 메모리 효율성을 높이는 방식과 가상 메모리의 상호작용을 간접적으로 이해합니다.
메모리 사용 패턴 코드 작성:
import java.util.ArrayList;
public class GarbageCollectionTest {
public static void main(String[] args) {
ArrayList<byte[]> data = new ArrayList<>();
for (int i = 0; i < 100; i++) {
data.add(new byte[10 * 1024 * 1024]); // 10MB 배열 추가
System.out.println("Iteration " + i);
if (i % 10 == 0) {
data.clear(); // 일부 데이터 삭제
System.gc(); // 가비지 컬렉션 요청
System.out.println("Requested GC.");
}
}
}
}
JVM 옵션 설정:
-verbose:gc 옵션을 추가하여 GC 동작을 모니터링합니다.결과 분석:
Spring Boot 애플리케이션의 메모리 사용량을 모니터링하고, 메모리 최적화를 통해 간접적으로 가상 메모리와 메모리 관리 효율성을 체험합니다.
Spring Boot 프로젝트 생성:
@RestController
public class MemoryController {
@GetMapping("/data")
public List<String> generateData() {
List<String> largeData = new ArrayList<>();
for (int i = 0; i < 10_000_000; i++) {
largeData.add("Data " + i);
}
return largeData;
}
}
JVM 메모리 모니터링:
top, htop).메모리 최적화:
@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> streamData() {
StreamingResponseBody stream = outputStream -> {
for (int i = 0; i < 10_000_000; i++) {
outputStream.write(("Data " + i + "\n").getBytes());
}
};
return ResponseEntity.ok().body(stream);
}
JVisualVM, Eclipse MAT 등을 사용해 메모리 사용 패턴 분석.가상 메모리 자체는 운영 체제와 하드웨어의 영역이지만, Java와 Spring 개발자의 입장에서는 메모리 부족 상황 시뮬레이션, 가비지 컬렉션 실습, Spring 애플리케이션의 메모리 최적화 등을 통해 간접적으로 가상 메모리의 동작 원리를 이해하고, 메모리 최적화를 위한 실용적인 경험을 쌓을 수 있습니다.