들어가며
우리는 자바에서 객체를 new 키워드로 만들곤 합니다. 하지만 객체를 생성은 하지만 삭제를 따로 하신 기억이 없으실 겁니다. 사실 객체를 생성한다는 말은 메모리에 객체에 관한 데이터를 올린다는 말입니다. 그렇다면 당연히 삭제하는 과정이 있어야 할 텐데 저희는 명시적으로 삭제를 하지 않죠. 그래도 문제없이 돌아가는 것은 GarbageCollector의 친구가 있었기 때문입니다.
동적할당은 Heap과 관련이 있다.
객체를 new 로 생성하는 것은 동적으로 할당을 한다는 말입니다. 동적 할당은 정적 할당과는 차이가 있는데 정적 할당은 이미 프로그램이 실행되기 전부터 할당이 정해져 있는 반면에, 동적 할당은 프로그램이 실행되는 환경(런타임 환경)에 되어서야 할당하기로 결정 나는 것이기 때문에 '동적'이라는 말이 붙었습니다. 이런 동적 데이터를 프로세스에선 Heap이라는 메모리 구조에 저장하게 됩니다.
그러면 Heap은 무엇인가?
메모리의 Heap 영역은 사용자가 직접 관리할 수 있는 메모리 영역입니다. 사용자에 의해 메모리 공간이 동적으로 할당되고 해제됩니다. Heap 영역은 메모리의 낮은 주소에서 높은 주소의 방향으로 할당됩니다. 이는 운영체제에 의해 실행될 프로세스에게 부여돼고 이후 관리는 JVM이 직접 합니다.
결국 Heap을 어떻게 구성할 지는 개발자 마음이다.
사용자가 직접 관리할 수 있기 때문에, 그 구성도 개발자 마음대로 할 수 있습니다. 이 주어진 공간을 최대한 효율적으로 사용하기 위해서 자바를 개발한 사람들은 전반적인 메모리 구조를 설계하였습니다. 여기에 대해 간략하게 알아보겠습니다.
2개의 Generation으로 구성된 Heap
Heap은 두 개의 Generation으로 구성됐습니다. 이렇게 한 근본적인 이유는 결국 메모리에 오랫동안 남는 객체(계속해서 참조되는 객체)가 적다는 것을 말하고 있는 약한 세대 가설(Weak Generational Hypothesis) 기반으로 설계했기 때문입니다. 쉽게 말해 어차피 메모리에 오래 남을 객체는 없으니까 최대한 메모리의 일부분만 계속해서 쓰고자 구성한 것입니다.
그렇게 Young과 Tenured Generation이 만들어집니다. 단지 메모리 구역에 이름을 붙였다고만 생각하시면 됩니다. 그러면 약산 세대 가설에 의해서 Young 부분만 계속해서 사용하게 될 확률이 높아진다고 보는 것이지요.
또한 Generation에 따라 Garbage Collection 이름을 다르게 부릅니다. Young 에선 Minor Collections 또는 Minor GC, Tenured에선 Major Collections 또는 Major GC 라고 합니다.
Garbage Collection? Garbage Collector?
무엇이 뚜렷하게 정해진 단어는 아닙니다. 혼용해서 사용하는 단어입니다.
거기다가 Garbage Collection은 필요 없는 메모리를 지우는 행위임을 의미할 때도 있고 그걸 실행하는 주체까지 포함하는 경우가 있습니다. 이처럼 범용적인 단어이니 너무 지엽적으로 파시면 혼동만 옵니다.
3개로 나뉜 Young Generation
Eden Space, Survivor Spaces(S0, S1)으로 나뉩니다. 이렇게 한 이유는 메모리 최적화 때문인데 이후에 나올 메모리 블럭 청소 알고리즘과 관련이 있습니다.
(1) Eden Space
모든 새롭게 초기화된 객체들은 여기에 만들어집니다. 만약 꽉 차면, minor GC를 수행합니다.
(2) Survivor Spaces
S0과 S1은 언제나 둘 중 하나만 사용합니다. 둘 중 하나가 꽉 차면 minor GC를 수행하고 살아남은 것들을 나머지 하나로 다 옮기고 그 곳에서 다시 새롭게 메모리를 채웁니다. 이렇게 두 개를 swap 하면서 계속 지우다가 가장 오래 살아남은 객체가 특정 임계값(threshold)에 도달하면 Tenured Space으로 promote 합니다. (단순히 그냥 장소를 옮긴다고 보시면 됩니다.)
Mark-and-Sweep 알고리즘
메모리 구조는 간략하게 알아봤습니다. 그러면 실질적으로 Garbage Collection을 어떻게 수행할까요? 이때 사용되는 것이 Mark-and-Sweep 알고리즘입니다. 이는 생각보다 매우 직관적인데, 지금 쓰는 객체를 Mark 한 뒤에 Mark 하지 않은 것들을 전부 다 탐색하면서 다 지워버리는 Sweep 을 진행합니다.
이에 관한 상세한 알고리즘들은 이후 글에서 다룹니다.
그러면 Garbage Collection 작업이 왜 성능에 영향을 주나요?
근본적인 질문이 드실 겁니다. 그냥 메모리 이렇게 갈면 되는데 왜 실제 성능에 크게 영향을 주는 지에 대해 말이죠.
이는 Garbage Collector가 Garbage Collection(이하 GC)를 수행할 때, 모든 어플리케이션의 스레드가 멈추기 때문입니다. 이를 Stop-the-World(STW) 라고 합니다. 메모리의 동시성을 지키기 위해서 그런 것인데요. 멀티 스레드 환경이라고 생각했을 때, 모든 스레드는 똑같은 데이터를 가지고 있어야 합니다. 다른 데이터를 가지고 있는 스레드가 생기면 알 수 없는 버그가 생기겠죠. 이걸 메모리 동시성이라고 하고 매우 중요한 문제인 것입니다.
그래서 GC 알고리즘을 어떤 것을 사용하는 지에 따라 어플리케이션 성능에 큰 영향을 줍니다. 이거 하나 바꿨다고 체감될 정도로 성능이 차이가 났다는 인터넷 사례들이 있는 걸 보시면 얼마나 큰 영향력을 가지는지 알 수 있습니다.
이쯤 되면 눈치 채셨을 겁니다. GC의 장단점을...
GC는 개발자가 메모리를 관리하지 않아도 된다는 매우 매우 큰 장점을 주는 대신에 예상치 못한 STW과 소프트웨어 복잡성 증가, 메모리 관리가 개발자에서 프로그램으로 가서 직접 관리 못하는 한계가 발생합니다.
이런 단점들에도 불구하고 현대 프로그래밍 언어들은 대부분 GC가 있습니다. 메모리 관리를 사람이 하는 게 얼마나 많은 문제를 초래하고 있는지 간접적으로 보여주는 것이죠.
결론
GC는 신이다.
하지만 어렵다.
이후 글에서 GC에 대해 더 자세히 다뤄보도록 하겠습니다.
참고
https://sematext.com/blog/java-garbage-collection/
What Is Garbage Collection in Java & How It Works
Learn what Java Garbage Collection is & how it works. Defining types of GC available, tutorials & best practices on how to work with them.
sematext.com
☕ 가비지 컬렉션 동작 원리 & GC 종류 💯 총정리
Garbage Collection(GC) 이란? 가비지 컬렉션(Garbage Collection, 이하 GC)은 자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객
inpa.tistory.com
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/generations.html
Generations
Measurement Throughput and footprint are best measured using metrics particular to the application. For example, the throughput of a web server may be tested using a client load generator, whereas the footprint of the server may be measured on the Solaris
docs.oracle.com
'백엔드 공부 > Java' 카테고리의 다른 글
[Java]JVM 구조 (1) | 2025.02.05 |
---|---|
자바(Java) 비동기 처리에 대하여 (0) | 2025.01.20 |