티스토리 뷰
JAVA는 C와 달리 개발자가 명시적으로 메모리를 건드리지(참조하지) 않는다. JAVA는 메모리를 Garbage Collection이라는 알고리즘으로 관리하며, 개발자가 메모리를 관리할 필요가 없도록 한다. (직접 관리하면 오히려 더 안좋은 경우가 많다.)
Garbage Collection은 말 그대로 '쓰레기를 정리하는 작업'이다. 그리고 JAVA의 메모리 입장에서 '쓰레기'는 곧 객체, 다시 말해서 '필요 없는 객체'이다.
1. GC와 Heap
Garbage Collection을 본격적으로 알아보기전에 JVM의 Runtime Area를 떠올려보자(생각이 안나거나, 잘 모르는 사람은 여기를 참고하자.) Runtime Area의 영역들은 스레드가 시작할 때 생성되는 경우가 있는가 하면, JVM이 시작할 때 생기는 영역도 있다. 후자가 바로 Heap이다.
Heap은 new 키워드로 생성된 인스턴스 및 배열이 저장되는 곳으로, 모든 스레드가 공유한다. 그리고 객체의 생성이 빈번한 JAVA의 특성상 메모리 이슈가 이곳에서 발생하게 된다. 그래서 Garbage Collection은 Heap에서 일어난다.
GC에 대해 이해하려면 Heap의 내부구조에 대해서도 어느정도 알아야한다. Heap은 크게 Young 영역과 Old 영역으로 구성된다. 그리고 Young 영역은 하나의 Eden 영역과 두개의 Survivor 영역으로 구성된다. 즉, JVM의 Heap은 4개의 영역으로 나뉜다고 할 수 있다.
2. GC의 전제조건과 시나리오
JAVA의 Garbage Collection은 메모리 관리를 '알아서' 한다고 했다. 이는 GC가 꽤 효율적임을 의미한다. Garbage Collection 알고리즘은 아래의 전제조건 하에 최적화되어 동작하도록 만들어졌다.
● 대부분의 객체는 금방 참조가 끊긴다.(접근 불가능 상태)
● 오래된 객체에서 젊은(생성된지 얼마 안된)객체로의 참조는 드물다.
위의 전제조건을 읽어보면, 대부분의 JAVA Application은 저렇게 동작하는 것 같다.(적어도 나는 그렇게 생각한다.) 그렇다면 이와 같은 조건에서 최적화 되어 동작하는 GC는 어떻게 일어나는지 알아보자.
먼저, 객체가 new 키워드로 생성될 때, Eden 영역에 생성된다.
그리고 Eden 영역이 꽉 차게 되면 Eden 영역에서 GC가 한번 일어나게 되며(참조가 끊긴 객체는 지워진다.) 여기서 살아남은 객체들은 Survivor영역 중 하나로 이동한다.
Eden 영역에서 GC가 일어나는 일이 반복되다가 하나의 Survivor영역이 가득 차게되면, 그 Survivor영역에서 GC가 일어난다. 그리고 여기서 살아남은 객체들은 나머지 Survivor영역으로 이동한다. 이때, GC가 일어난 Survivor영역은 비어있어야 한다.
Survivor1 영역과 2영역을 왔다 갔다하는 과정을 반복하다가 계속 살아있는 객체는 Old 영역으로 이동한다. 만약 Eden영역의 살아있는 객체가 Survivor영역보다 더 큰 경우, 바로 Old영역으로 옮겨진다.
3. GC 방식
GC 방식에 대해 다루기 전에 알아야할 개념이 stop-the-world이다. stop-the-world란, GC을 실행하기 위해 JVM이 모든 애플리케이션 실행을 멈추는 것이다. stop-the-world가 발생하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 작업을 멈춘다.
모든 GC 방식에서는 stop-the-world가 발생한다. 이러한 stop-the-world를 줄이는 것이 GC 튜닝이라고 할 수 있다. 한편 GC 방식에는 총 5가지가 있다.
● 시리얼 콜렉터 (Serial Collector)
● 병렬 콜렉터 (Parallel Collector)
● 병렬 압축 콜렉터 (Parallel Compacting Collector)
● CMS 콜렉터 (CMS Collector)
● G1 콜렉터 (Garbage First Collector)
시리얼 콜렉터
Young 영역의 GC은 바로 위에서 설명한 바 있다. 다시 한번 그림으로 짧게 설명하고 넘어가겠다.
그리고 Old 영역의 객체들은 Mark-sweep-compact 알고리즘을 따른다. 이 알고리즘의 동작은 이름만으로도 유추할 수 있다. 살아있는 객체를 식별하고(Mark), Old영역의 가장 앞부분 부터 살아 있는 것만 남기고 삭제하며(Sweep), 마지막으로 살아있는 객체들을 가장 앞쪽으로 모아준다.(Compact)
Mark-sweep-compact 단계를 거친 Old영역은 다음과 같은 상태가 된다.
이 방식은 굉장히 느리다. 그렇지만 그만큼 시스템 자원을 덜 사용한다. 속도와 별 상관이 없고, 시스템 자원이 부족할 때 사용하는 GC 방식이다.
병렬 콜렉터
병렬 콜렉터 방식의 경우 시리얼 콜렉터 방식과 GC 알고리즘은 같다.(Young영역과 Old영역 모두 같다.) 하지만 단일 스레드가 GC를 수행하는 시리얼 콜렉터 방식과 달리, 병렬 콜랙터 방식은 GC를 처리하는 스레드가 여러개이다. 당연히 시스템 자원이 넉넉할 때 유리한 방식이다.
출처: https://www.cubrid.org/blog/understanding-java-garbage-collection
병렬 압축 콜렉터
병렬 콜렉터 방식과 비슷하다. 다만 Old 영역의 GC에서 Mark-Summary-Compaction 알고리즘을 채택했다. 결국 Sweep과 Summary의 차이라고 할 수 있다.
Sweep은 단일 스레드가 Old 영역 전체를 훑어 살아있는 객체만 찾아내는 방식이지만, Summary는 여러 스레드가 Old 영역을 분리하여 훑는다. 또한 효율을 위해 앞선 GC에서 Compaction된 영역을 별도로 훑는다.
CMS 콜렉터
CMS 콜렉터 방식의 Young 영역 GC 방식은 병렬 콜렉터의 그것과 같다. 다만 Old 영역에서는 다음과 같은 단계를 거친다.
● Initial Mark
● Concurrent Mark
● Remark
● Concurrent Sweep
Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중에서만 살아있는 객체를 찾는다. 그러므로 멈추는 시간이 매우 짧다. Concurrent Mark 단계에서는 Initial Mark 단계에서 살아남은 객체의 참조를 따라가며 살아있는 객체를 찾는다.(이 때 여러개의 스레드가 동작한다.)
Remark에서는 Concurrent Mark를 수행하는 동안 객체의 참조가 끊기거나, 새롭게 생긴 객체가 없는지 다시한번 확인한다. 마지막으로 Concurrent Sweep에서는 쓰레기를 정리한다. 별도의 Compaction이 없음에 유의한다.
CMS 콜렉터 방식은 stop-the-world 시간이 매우 짧다는 장점이 있다. 하지만 그만큼 시스템 자원(메모리, CPU)를 더 많이 사용하고, Compaction 단계가 없어 Old영역의 크기가 충분하지 않거나 크기에 비해 조각난 메모리가 많을 경우 오히려 stop-the-world 시간이 늘어날 수 있다.
G1 콜렉터
JAVA7에서 새롭게 소개된 GC방식이다. 다음 그림을 보자
앞서 소개한 GC방식들은 모두 Young영역과 Old영역이 정해져서 나뉘어있었지만, G1 콜렉터 방식에서는 바둑판 모양의 영역이 각각 Eden, Survivor, Old영역의 역할을 동적으로 바꿔가며 GC가 일어난다.
Young 영역의 GC와 Old 영역의 GC는 모두 CMS콜렉터 방식과 유사하다. 이부분에 대해서는 Oracle의 자세한 설명이 쓰여있는 페이지를 소개하고 마무리하도록 하겠다.
http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
출처 및 참고
http://d2.naver.com/helloworld/1329
http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html
이상민, 『개발자가 반드시 알아야 할 자바 성능 튜닝 이야기』, 인사이트(2013)
-끝-
'IT > 기술면접' 카테고리의 다른 글
[JAVA] Interface vs Abstract Class (0) | 2020.07.05 |
---|---|
트랜잭션에 대하여 (0) | 2018.11.24 |
JAVA의 Exception(예외)이란 무엇인가 (0) | 2018.02.02 |
싱글턴(Singleton)패턴에 대해 자세히 알아보자 (1) | 2018.01.07 |
[JAVA] String, StringBuilder, StringBuffer에 대하여 (0) | 2017.10.05 |