🍎 저수준에서 힙영역 메모리 할당에 관하여 알아보고 운영체제의 상을 차용한 JVM 에선 어떤 차이가 있는지 알아보는 글입니다.
(저수준의 언어 == C언어)
🍏 프로세스 주소 공간과 구성 요소의 역할
- 프로세스의 주소 공간은 위 이미지와 같이 구성되며, 코드 영역은 낮은 주소부터 시작하고, 스택 영역은 높은 주소부터 하향식으로 동적으로 확장됩니다.
- Code Area(코드 영역)
- 프로세스의 주소 공간의 코드 영역에는 프로그래머가 작성한 코드, 더 정확하게는 컴파일한 후 생성된 실행 가능한 명령어가 저장됩니다. 스레드가 공유하는 영역이며 프로그램이 실행되는 동안은 코드를 수정할 방법이 없으므로 스레드 안전 문제가 발생하지 않습니다.
- Data Area(데이터 영역)
- 데이터 영역은 전역 변수가 저장되는 곳입니다. 해당 영역은 스레드가 공유합니다.
- Heap Area(힙 영역)
- 힙 영역은 메모리의 동적 할당에 사용되는 영역으로 프로그램 런타임 시점에 malloc(C), new(Java)를 통해 힙에 메모리할당, 객체가 할당됩니다. 해당 영역은 스레드가 공유합니다.
- Stack Area(스택 영역)
- 프로세스 메모리 구조에서 함수 호출과 지역 변수 등을 저장하는 역할을 수행하며 LIFO 방식으로 동작합니다. 각 스레드의 고유한 영역이며 스택 영역은 고정된 크기를 갖습니다. (프로그래머가 임의로 변경할 수 있음)
- 함수 A가 함수 B를 호출한다면 함수 A는 매개변수를 상응하는 레지스터에 저장하며, CPU가 함수 B를 실행할 때, 이 레지스터에서 매개변수 정보를 얻을 수 있습니다. 그러나 CPU 내부의 레지스터 수 (쉽게 말해 임시저장소)는 제한되어 있는데 이때 스택 프레임을 사용해 나머지 매개변수를 저장할 수 있습니다.
- 프로세스 메모리 구조에서 함수 호출과 지역 변수 등을 저장하는 역할을 수행하며 LIFO 방식으로 동작합니다. 각 스레드의 고유한 영역이며 스택 영역은 고정된 크기를 갖습니다. (프로그래머가 임의로 변경할 수 있음)
- Free Area(유휴 영역)
- 프로세스 주소 공간의 여유 공간을 의미하며 스택은 주소 상단에서 하단으로 힙 영역은 할당될 때마다 하단에서 상단으로 메모리를 점유할 수 있습니다.
- 동적 링크에 사용되는 라이브러리 코드와 데이터가 존재하며 모든 스레드가 접근해 사용할 수 있습니다.
- 프로그램이 동작 중에 특정 파일을 열면 프로세스 주소 공간에 열린 파일 정보도 저장됩니다.
❓ 프로세스 수준에서 주소 공간과 구성 요소에 대하여 정리한 이유는 무엇인가요?
- JVM(프로세스)이 응용 계층에서 개념을 차용해 구동되기 때문입니다.! JVM 역시 아래와 같은 메모리 구조를 갖고 있습니다. 생긴 모습은 차이가 있지만 큰 틀에서 개념은 동일합니다.
🍏 저수준에서 유휴 영역이 부족할 때, 발생하는 일
- C언어 레벨(Java 보다 저수준, 추상화되어 있으며 HW에 접근 가능)에서 동적 영역에 값을 할당하는 방법은 malloc() 함수를 사용하여 달성합니다. malloc 함수는 표준 라이브러리에 존재하며 프로그래머는 메모리를 할당할 때 세부사항(어떻게 메모리가 할당되는지)을 신경 쓰지 않고 사용할 수 있습니다.
- malloc(). 즉, 메모리를 할당받을 때 진행되는 순서
- 프로그램은 malloc을 호출하여 메모미 할당을 요청합니다.
- malloc은 여유 메모리 조각을 검색하기 시작하고 적절한 크기의 조각을 찾으면 할당합니다. 이 단계는 사용자 상태에서 처리됨.
- malloc이 여유 메모리 조각을 찾지 못하면 (== 힙 영역이 충분하지 않다면) brk 시스템 호출을 통해 OS에게 메모리 추가 할당을 요청합니다. brk는 운영체제의 사용이 필요한 부분이므로 커널 상태에 놓여 있습니다. 결과적으로 힙 영역이 늘어나면 여유 메모리 조각을 찾아 할당합니다.
✓ 표준 라이브러리를 사용하게 되면 시스템의 차이를 감출 수 있습니다. 윈도와 리눅스 시스템 호출은 전혀 다르지만 표준 라이브러리를 사용하므로 작성한 프로그램의 추가적인 수정 없이 서로 다른 운영체제에서 실행할 수 있습니다.
🤔 궁금한 점이 생겼습니다. JVM에선 유휴 공간이 없을 때 추가적으로 메모리 할당을 한다는 이야기를 들어보지 못했는데 추가적인 공간이 필요할 땐 어떻게 대응할까요?
🍏 JVM에서 메모리 크기 할당
- .jar file을 실행시킬 때, java -Xms512 m -Xmx2 g -jar yourfile.jar의으로 초기 힙 사이즈, 최대 힙 사이즈를 설정해 실행시킬 수 있습니다.
- 힙 메모리가 부족할 때, JVM의 GC Process가 구동되며 사용하지 않은 객체들의 메모리를 수거하게 됩니다. 그럼에도 불구하고 힙 메모리가 부족할 경우, OutOfMemory Error가 발생해 프로그램이 비정상적으로 종료됩니다.
❓ 그렇다면 JVM에선 추가적으로 메모리를 할당하지 못하는 이유는 무엇일까요?
- 메모리 관리 주체가 프로그래머가 아닌 JVM이기 때문입니다.
- C언어에서는 프로그래머가 직접 메모리를 할당하고 해제하는 방식으로 메모리를 관리합니다. 메모리가 필요하게 되면 malloc을 사용해 OS에게 요청하게 되고 할당받은 공간 또한 프로그래머 관리하에 다뤄집니다.
- 반면, JVM 환경에서는 초기에 메모리를 할당하고 JVM 최적화를 통해 메모리를 관리합니다.
- 만약 JVM이 운영 체제로부터 동적으로 메모리를 할당받게 된다면, JVM은 힙 메모리, 메타스페이스, 스택 등 여러 메모리 영역을 관리하는데, 이때 JVM 내부에서 관리하는 메모리 영역들의 크기와 일관성에 문제가 발생할 수 있습니다. 추가적으로, GC의 비효율성, 메모리 단편화, 메모리 관리와 충돌 등의 이슈가 발생할 수 있습니다.
📝 결론
- Trade Off를 했다고 생각합니다.
- 동적으로 저장공간을 할당받을 수 있는 C언어와 달리 JVM은 초기에는 상대적으로 큰 저장공간을 할당받고 이를 최적화하여 관리하는 전략을 택했습니다.
- 물론 VM으로 개발한 이유는 여러 가지가 있겠지만, 이번 글을 작성하면서 개발 과정에서 선택을 해야 하는 순간에는 명확한 근거를 바탕으로 결정을 내려야 한다는 점을 깊이 고민하게 되었습니다.
📚 Reference
'Dev' 카테고리의 다른 글
[Dev] 서버 클라이언트 사이 버전을 관리하는 방법 (2) | 2024.10.16 |
---|---|
Enum을 사용할 때 생각해봐야할 점 (2) | 2024.09.26 |
Spring @Value를 통해 환경 변수를 주입받을 때 주의할 점 (0) | 2024.09.16 |
DB CRUD 작업의 비용 분석 (1) | 2024.09.15 |
시간값 발행 주체는 누가 되어야 하는가? (0) | 2024.09.03 |