자바에서 일반적으로 구성되는 메모리의 구조는 아래와 같습니다.

Stack 영역
- 스택 프레임을 저장하는 메모리 공간
- 스택 프레임은 메소드가 호출되기 이전의 상태를 기록하는 역할을 수행하며 아래의 내용을 포함한다.
- 매개 변수 : 호출 메소드가 전달한 인자 값
- 지역 변수 : 메소드내에서 선언한 변수
- 리턴 주소 : 메소드 실행을 마친 다음 실행할 명령문의 주소
- 리턴 값 : 호출 메소드에게 돌려줄 값
- 즉, 메소드 내에서 정의하는 기본 자료형(int, boolean, double, byte 등 )에 해당하는 지역 변수와 매개 변수의 데이터 값은 스택 프레임에 포함되어 Stack Area(Stack 영역)에 저장됩니다.
- 스택 프레임은 메소드가 호출될 때마다 메모리에 할당(Push)되고, 종료되면 메모리에서 제거(Pop)됩니다.
- 기본형 타입 변수의 값들은 Stack 영역에 저장되지만, 참조형 타입 변수(객체 등)는 참조 값만 Stack 영역에 저장된다.
- 객체들은 Heap 영역에 실제로 저장되지만, Stack 영역에서 Heap 영역의 객체들에 대한 참조를 가지고 있다.
- Stack 영역의 top에 존재하는 하나의 스택 프레임만 활성화되며, 그 이전에 존재하는 스택 프레임은 모두 비활성화되며, 비활성화된 스택 프레임의 지역 변수에는 접근이 불가능합니다.
- 위의 이미지에서 Stack 메모리가 여러겹 중첩되어 있는 것을 볼 수 있는데, 이는 Stack 메모리 영역이 스레드 별로 할당되기 때문이다.
- 따라서 스레드가 생성되고 시작될때마다 각각의 Stack 메모리를 가지게 되며, 다른 스레드의 스택 메모리에 액세스할 수 없습니다.
Heap 영역
- Heap 메모리 영역에는 실제 객체가 저장된다.
- Heap 영역에 존재하는 객체들은 Stack 영역의 변수들에 의해 참조된다.
StringBuilder sb = new StringBuilder();
- 위의 예시에서 new 라는 키워드는 Heap 영역에 StringBuilder 라는 객체를 생성하기에 충분한 빈 공간이 있는지 확인해야 하고, Stack에 있는 sb 라는 변수를 통해 참조되도록 보장한다.
- 실행중인 각 JVM 프로세스에 대해서 Stack과 달리 Heap 메모리 영역은 단 하나만 존재한다.
- 따라서 실행중인 스레드의 수에 관계없이 Heap 영역에 존재하는 메모리는 공유된다.
- Heap 영역에는 가비지 콜렉터 프로세스를 용이하게 하는 몇 개의 부분으로 나뉩니다. 아래에서 자세하게 다뤄보겠습니다.
Reference Types
- 자바에서는 참조되는 Heap의 객체들은 가비지 컬렉팅의 기준에 따라 여러 Reference Type으로 분류된다.
- String Reference : 강력한 참조 -> 위의 이미지에서 두꺼운 선
- 가장 익숙한 참조 유형입니다. 위의 StringBuilder 예제에서 힙 영역에 존재하는 객체에 대한 강력한 참조를 가지고 있다.
- 힙 영역에 존재하는 객체들은 이러한 강력한 참조가 있는 경우 혹은 강력한 참조와 연결되어 있는 경우, 가비지 컬렉터에 의해 제거되지 않습니다.
- Weak Reference : 약한 참조'
- 가비지 컬렉터가 수행된다면 이에 의해 제거될 수 있다.
- -> 무조건 제거되는 것이 아니라, 참조되는 객체가 null이 되고, 해당 객체를 가리키는 참조가 Weack Reference일 때만.
Integer i = 1; WeakReference<Integer> weak = new WeakReference<Integer>(i);
- 가비지 컬렉터가 수행된다면 이에 의해 제거될 수 있다.
- 위처럼 WeakReference 클래스를 사용하고, i = null을 수행하면 i == null이 true인 경우 가비지 컬렉터에 의해 제거된다.
- Soft Reference : 부드러운 참조
- 응용프로그램의 메모리가 부족한 경우에만 가비지 콜렉터에 의해 제거되므로 메모리에 무척 민감한 상황에서 사용된다.
- 일부 메모리 공간을 확보할 필요가 없는 경우에는, 가비지 컬렉터가 이 객체를 제거하지 않는다.
- 자바는 모든 Soft Reference 유형의 객체가 OutOfMemoryError가 발생하기 전에 제거되도록 보장한다.
- 아래와 같이 생성할 수 있다.
SoftReference<StringBuilder> reference = new SoftReference<>(new StringBuilder());
- Phantom Reference : 유령 참조
- 객체가 더 이상 살아있지 않다는 것을 확실히 알고 있기 때문에, 사후 정리 작업을 예약하는데 사용된다.
- 유령 참조의 get 메소드는 항상 null을 반환하기 때문에, 참조 Queue에서만 사용된다.
- 요약하자면, 사용이 아닌 올바르게 삭제하고, 삭제 이후에 작업을 조작하기 위해서 사용되는 것이다.
- 자바에서는 finalize() 라는 메소드가 존재하며, 이 메소드는 가비지 컬렉터에 의해 호출되지만, 이는 몇가지 문제점을 가지고 있다.
- finalize() 메소드를 잘못 사용하면 가비지 컬렉터에 의해 제거된 객체가 부활할 수도 있다.
- 만약 finalize() 에서 String Reference를 갖도록 코드를 작성하면 부활한다.
- 그러나 유령 참조는 메모리에서 해제된 후 enqueueu(입력)되기 때문에 객체가 부활하는 문제가 없다.
가비지 컬렉션 프로세스
- Stack의 변수가 Heap의 객체에 대해서 보유한 참조 유형에 따라 가비지 컬렉터에 의해 수집되는 시점이 다르다.

- 위 이미지에서 빨간색으로 표시된 Heap 영역의 객체들은 가비지 컬렉터에 의해 수거된다.
- 오른쪽 상단에 보면, 힙 영역의 객체가 다른 두 객체에 대한 강력한 참조가 있는 것을 알수있지만, 해당 객체는 스택 영역에서 참조되지 않는 객체이기 때문에 더 이상 도달할 수 없는 객체여서 가비지 컬렉터에 의해 수거된다.
- 특징
- 자바에 의해 자동적으로 실행되며, 언제 작동할지는 자바가 결정한다. -> 우리가 알 수 없고, 조절도 불가
- 사실 비용이 많이 드는 과정이다. 실행된다면 현재 실행중인 애플리케이션의 모든 스레드가 일시중지된다.
- 가비지 수집과 메모리 해제보다 훨씬 더 복잡한 프로세스이다.
- 가비지 컬렉터를 System.gc()로 호출할 수는 있지만, 호출했다고 해도 자바가 작동을 결정하기 때문에 사실상 권장되지 않는다.
- 가비지 컬렉션 프로세스는 매우 복잡하며, 성능에 영향을 줄 수 있기 때문에 "Mark and Sweep" 방식이라는 방식이 사용된다
- 자바는 스택 영역의 변수들을 분석하고, 활성화 상태로 유지해야 하는 모든 객체에 대해 Mark를 남깁니다.
- 그리고 Mark 되지 않는 객체들을 모두 정리한다.
- 쓰레기가 더 많아지고, 활성 상태로 Mark되는 객체들이 적을수록 프로세스는 더 빨라진다.
- 이 과정을 더욱 최적화시키기 위해서 힙 메모리는 여러 부분으로 구성된다.

- 객체가 생성되었을 때 해당 객체는 위 이미지의 1번 영역인 Eden 영역에 할당된다. Eden 영역은 크지 않기 때문에 빠른 속도로 가득 차게된다. 이때 가비지 컬렉터는 Eden 공간에서 객체를 활성 상태로 Mark 한다.
- 만약 객체가 가비지 컬렉션 프로세스에서 살아남는다면 생존 공간이라 불리는 2번 영역인 S0영역으로 이동하게 된다.
- 이후에 가비지 컬렉터가 Eden 영역에서 또 실행되어 또 살아남는다면 모든 객체들은 3번 영역인 S1 영역으로 이동하게 된다.
- 객체가 X번째의 가비지 컬렉션에서 살아남으면 4번 영역인 Old 영역으로 넘어간다. -> x는 JVM 구현에 따라 다르다
- Old 영역에서도 가비지 수집이 가능하지만, Eden 영역보다 큰 메모리 비중을 차지하기 때문에 자주 발생하지는 않는다.
정리
- Young 영역
- Eden 영역과 Survivor 영역(S0, S1)이 존재
- 새롭게 생성된 객체가 할당되는 영역
- 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라진다.
- Young 영역에 대한 가비지 컬렉션을 Minor 가비지 컬렉터라고 부른다
- Old 영역
- Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- 복사되는 과정에서 대부분 Young 영역보다 크게 할당되며, 크기가 큰 만큼 가비지는 적게 발생한다.
- Old 영역에 대한 가비지 컬렉션은 Major 가비지 컬렉터 또는 Full 가비지 컬렉터라고 한다.
가비지 컬렉터 타입
- Serial 가비지 컬렉터 : 직렬 가비지 컬렉터
- 단일 스레드 수집기이다. 주로 데이터 사용량이 적은 소규모 애플리케이션에 적용된다.
- Parallel 가비지 컬렉터 : 병렬 가비지 컬렉터
- 여러 스레드를 사용하여 가비지 컬렉션 프로세스를 수행한다. 해당 유형의 가비지 컬렉터를 Throughput Collector 라고도 부른다.
- Mostly Concurrent 가비지 컬렉터 : 대부분 동시성 가비지 컬렉터
- 애플리케이션과 동시에 작동하는 가비지 컬렉터지만, 대부분 동시성이어서 응용프로그램과 100% 동시에 작동하지는 않는다.
- 스레드가 일시 중시되는 기간이 있지만, 최고의 가비지 컬렉터 성능을 달성하기 위해 가능한 짧게 유지된다.
- 2가지 유형
- Garbage First : 합리적인 애플리케이션의 일시중지 시간으로 높은 스루풋을 가진다.
- Concurrent Mart Sweep : 애플리케이션의 일시중지 시간이 최소로 유지된다. 그러나 JDK 9 부터 이 유형은 사용되지 않는다.
참고 자료
'CS > 운영 체제' 카테고리의 다른 글
[백엔드] 리눅스 터미널 명령어 (0) | 2023.03.31 |
---|---|
[백엔드] 스레드 (0) | 2023.03.30 |
[백엔드] 운영 체제 2 (0) | 2023.03.30 |
[백엔드] 운영 체제 1 (0) | 2023.03.30 |
댓글