-
[Java Multithreading] 메모리 영역 - Stack과 HeapJava/Multi Threading 기초 2022. 10. 25. 13:57
Java Multithreading
멀티스레딩의 개념과 Java에서의 활용법을 공부하고 정리하는 시리즈입니다.
오늘은 멀티스레드 애플리케이션을 구현할 때 고려해야 하는 기초적인 메모리 지식에 대해 이야기 해보려고 합니다.
달리 말해 스택과 힙 이야기인데요. 각 메모리 영역의 특성과 어떤 것들을 포함하는지를 생각해 보겠습니다.Stack
스택은 각 스레드마다 생성되는 메모리 영역입니다. 이 영역에 포함되는 것은 다음과 같습니다.
- 호출된 메서드와 현재 실행 중인 구문을 표시하는 명령어 포인터(Instruction Pointer)
- 인자(argument)와 지역변수
- 메서드 리턴 값을 저장하기 위한 공간
메서드는 하나의 프레임으로 쌓이고 메서드에 사용되는 변수들은 해당 프레임 안에 쌓입니다. 이 하나의 프레임은 하나의 메서드가 전용으로 사용합니다. 이것을 스택프레임(Stack Frame)이라고 합니다.
스택 영역에서는 스레드가 실행 중인 구문을 다룬다는 것이 중요합니다.
하나의 프로그램을 실행하더라도 스택 영역은 서로 공유하지 않습니다. 별개의 스레드이기 때문입니다.
또한 호출되지 않은 것은 스택에 존재하지 않으며, 호출된 것도 순차적으로 쌓이고 제거됩니다. 그렇기 때문에 스택인 것이죠.void main(String[] args) { int x = 1; int result = someMethod(x); } int someMethod(int a) { return a; }
예시 코드입니다. 먼저 실행된 메인 메서드 스택프레임에
int x
라는 변수를 할당했습니다.someMethod()
를 호출하는 부분에서 someMethod의 스택프레임이 생성될 것입니다. 그리고int a
라는 변수(인자)가 할당됩니다. 이때a
는x
를 넘겨서 만들어진 같은 값이지만 엄연히 별개의 스택프레임에 존재하는 별개의 변수입니다. 단지x
의 복사복일 뿐이죠. 그렇기 때문에a
의 값을 변경해도 메인 스택프레임의 변수x
에는 변화가 없는 것입니다.명령어 포인터가 someMethod의
return
을 만나면 리턴값을 레지스터에 임시로 저장하여 호출한 곳으로 다시 전달되게끔 준비합니다. 그러고나서 포인터가 다시 메인으로 돌아오면 someMethod 스택프레임은 해제됩니다.스택에 대해 마지막으로 정리하겠습니다.
- 스레드가 실행 중인 모든 변수는 스택에 존재합니다.
- 스택은 스레드가 생성될 때 정적으로 할당됩니다.
- 그렇기 때문에 스택의 사이즈는 런타임 중에는 변경될 수 없으며, 대체로 작은 크기를 가집니다.
- 또한 그렇기 때문에 스택 프레임이 너무 많이 쌓이게 되면(예를 들어 메서드 재귀호출), Java에서는
StackOverflow
예외가 발생하게 됩니다.
Heap
이제 힙(Heap)을 살펴 보겠습니다.
힙은 프로세스를 위한 공유 메모리 영역의 데이터입니다. 같은 프로세스에 속해있는 스레드들은 힙에 접근하거나 객체를 할당할 수 있습니다.
객체를 할당한다는 말이 나온 김에 힙에는 어떤 것들이 포함되는지 이야기 해보겠습니다.- 생성된 모든 객체(instance)들
- 생성된 객체의 모든 멤버 변수들
- 배열
약간의 설명을 덧붙이면, 생성되는 객체는 모두 힙에 할당됩니다. 객체의 참조를 담고 있는 변수는 지역변수일 수 있지만, 그 지역변수가 가리키는 참조된 객체는 힙에 존재합니다. 말이 나온 김에 강조하자면, 참조와 참조 대상인 객체(instance)는 별개라는 것을 꼭 인지해야 합니다. 지역변수는 스택에 위치하며(그러니까, 지역변수로 정의된 참조변수는 스택에 쌓이며), 생성된 인스턴스와 인스턴스 변수만이 힙 상단에 위치하게 됩니다.
멤버 변수들 또한 마찬가지입니다. 심지어 원시 타입 변수라고 해도, 인스턴스에 속한 멤버 변수는 다른 객체 타입 변수와 마찬가지로 공유되는 되는 자원입니다.힙은 JVM의 가비지 콜렉터(Garbage Collector, GC)가 관리합니다. 해당 객체를 가리키는 모든 참조가 사라지게 되면 GC의 청소 대상이 됩니다(JVM의 각 영역에 대한 내용은 추후 이야기 해보겠습니다).
객체와 멤버 변수의 생명주기는 동일한데, 객체를 청소할 때에 객체에 내포된 멤버 변수도 함께 청소되기 때문입니다.Data / Method Area
프로세스에는 Data(또는 Storage라고도 불리는)라는 이름의 영역(segment)이 존재하는데요. JVM에는 이 Data와 Text라는 프로세스의 영역을 모티브로 만든 것 같은 Method Area라는 이름의 공유 영역이 존재합니다.
이 영역은static
으로 지정된 정적 정보와, 런타임에 생성되는 상수 풀, 멤버 변수와 메서드 데이터, 그 외 클래스 초기화에 관련된 여러 메타 정보를 가지고 있는 영역입니다.
메서드 영역은 JVM이 시작될 때 생성되고, (대체로) 논리적으로는 힙의 일부로 구현되어 있습니다. 이 영역에 대한 GC의 작동 여부는 구현에 따라서 다릅니다.
말이 조금 복잡하지만, 위에 굵은 글씨로 적었듯 '공유 되는 정적 정보에 대한 영역'이란 점을 이해하신 다면 이후의 포스팅을 이해하시는 데에는 전혀 문제가 없으실 거라고 생각되어요.마무리
중요한 것은 힙은 공유되는 자원이 자리잡는 메모리 영역이고 스택은 스레드 별로 각각 할당되는 자원이 위치한 영역이라는 것입니다.
애플리케이션을 구현할 때에는 이 중요한 차이로 인해 생기는 문제나 이점에 대해 고려할 수 있어야 합니다.
수정 이력
2022-10-27 : o_b:us님의 의견 - "static자원들은 heap영역보단 data(static)영역에 할당되는게 아닌가"라는 의견에 따라 'Data / Method Area' 파트를 추가.
관심과 도움에 감사드립니다.
'Java > Multi Threading 기초' 카테고리의 다른 글
[Java Multithreading] 원자적 연산과 volatile (1) 2022.11.10 [Java Multithreading] 임계 영역과 동기화 (0) 2022.10.28 [Java Multithreading] 리소스 공유와 임계 영역 (0) 2022.10.27 [Java Multithreading] 성능 최적화의 두 관점 - 처리량과 http 서버 (0) 2022.10.24 Java Multithreading [4] - 성능 최적화의 두 관점 - 지연 시간 (0) 2022.08.29 Java Multithreading [3] - Thread#join() : Thread 간을 조정하는 방법 (0) 2022.08.23