[Java] GarbageCollector에 대한 정리
업데이트:
개요
GC(Garbage Collector)는 다음과 같은 의미를 가진다.
- 어떤 객체가 사용중이고 어떤게 사용중이 아닌지 판별하거나 사용되지 않은 객체를 삭제하며 힙 메모리를 바라보는 과정
- 사용중인 객체나 참조되고 있는 객체가 의미하는 것은 프로그램이 여전히 그 객체에 대한 포인터를 유지하고 있다는 의미.
- 사용되지 않은 객체나 참조되지 않는 객체는 참조되지 않는 객체에 의해 사용되고 있는 메모리가 reclaimed될 수 있게하기 위해 더이상 당신이 작동시키고 있는 프로그램을 참조하지 않는다.
C 같은 언어(Unmanaged)에서는 메모리를 할당하거나 해제하는 것은 당연한 일이다. 자바에서는 메모리를 해체하는 과정은 GC에 의해 자동적으로 일어난다. 기본적인 과정은 아래와 같이 묘사될 수 있다.
GC 기본 동작 과정
Step 1. Marking
과정의 가장 첫 단계는 마킹이라고 불린다. 이 과정은 GC가 메모리의 어떤 부분이 사용되고 어떤 부분이 사용되고 있지 않은지 확인하는 과정이다. 참조된 객체는 파란색으로 표시되어 있다.
참조되지 않는 객체는 금색으로 표시되어 있다. 모든 객체는 이러한 표시(결정)을 하기 위해 마킹절에서 스캔된다. 시스템안의 모든 객체가 스캔되어야 한다면, 이는 시간을 매우 많이 소비하는 과정이 될 수 있다.
Step 2. Normal Deletion
이 단계에서는 참조되지 않은 객체를 제거하고 참조된 객체와 포인터를 여분 공간에 남긴다.
메모리 할당자(allocator)는 새 객체가 할당될 수 있는 여분 공간의 블록에 참조를 유지한다.
Step 2-a. Deletion with compacting
성능을 더욱 개선시키고 참조되지 않은 객체를 삭제하기 위해 당신은 남아있는 참조 객체를 더욱 경량화(compact) 시킬 수 있다. 참조된 객체를 함께 움직임으로써 새로운 메모리 할당을 더욱 빠르게 할 수 있다.
그럼에도 불구하고 왜 일반적인 Garbage Collection을 써야하는가 ?
이전에도 언급했듯, JVM 안에 있는 객체들을 모두 경량화(compact)시키고 마킹하는 것은 비효율적이다.
객체들이 할당되면 할당 될수록 객체의 리스트 들은 점점 garbage collection time을 늘이게 된다. 그러나, 어플리케이션에 대한 경험적 분석은 객체들의 생명주기가 그렇게 길지 않다는 것을 보여준다.
여기 아래에 할당된 바이트 수를 나타내는 Y축과 시간 경과에 따라 할당된 바이트 수를 타나내는 X축을 기준으로 그려진 그래프가 있다.
이곳에서 볼 수 있듯, 그래프의 왼쪽의 높은 수치에 의해 시간이 지남에 따라 할당되어 있는 객체가 적으면 적을수록 실제 대부분의 객체는 짧은 생명주기(short life)가지고 있다는 것을 보여주고 있다
JVM Generations
객체 할당행위를 통해 얻은 정보는 JVM 성능을 강화하는데 사용될 수 있다. 그러므로, heap
은 작은 parts
나 generation
으로 쪼개진다. heap part 들은 다음과 같다.
Young Generation
Old/Tenured Generation
Permanent Generation
Young Generation
Young Generation은 모든 새로운 객체들이 할당되고 성숙화되는 곳이다. Young Generation이 가득 차면, 이것은 minor garbage collection이 발생된다.
Minor Collections는 높은 객체 파괴율을 가정해 최적화할 수 있다.
죽어있는 객체들로 가득차있는 young generation은 매우 빠르게 수집된다. 몇몇 살아있는 객체들은 성숙화(aged)되고 결국 old generation으로 옮겨진다.
Stop the World Event
모든 minor garbage collection 들은 그 작동(operation) 이 완료될 때 까지 모든 어플리케이션의 스레드들이 멈추는 “ Stop the World
” 이벤트이다. minor garbage collections들은 항상 세상(어플리케이션)을 멈추는 이벤트이다.
Old Generation
old generation은 오래 살아남은 객체들을 저장하기 위해 사용된다. 전형적으로, young generation 객체에 대해 임계값이 설정되고 해당 임계값을 마주하면 그 객체는 old generation으로 이동된다. 결국 old generation 도 수집이 필요해지게 된다. 이러한 이벤트는 major garbage collection이라고 불린다.
major garbage collection들 또한 세상을 멈추는 이벤트이다. major collection은 살아있는 객체를 포함하기 때문에 종종 훨씬느리다. 그래서 반응형 어플리케이션의 경우 major garbage collection은 최소화 되어야 한다. 또한 major garbage collection의 Stop the World 이벤트의 길이는 old generation 공간에 사용된 garbage collector의 종류에 영향을 받는다는 것을 기억해두자.
Permanent Generation
Permanent generation은 어플리케이션 안의 클래스와 메서드들을 묘사하기 위해 JVM에 의해 필요한 메타데이터를 포함한다. Permanent generation은 어플리케이션에 의해 사용되고 있는 클래스들에 기반한 동작시간(run time)에 있는JVM에 의해 채워진다. Java SE 라이브러리 클래스들과 메서드들은 이곳에 저장될 수 있다.
만약 JVM이 어떤 클래스가 더이상 필요하지 않고 다른 클래스들을 위한 공간이 필요로 해진다면 그 클래스들은 수집(collected, unloadded)될 수 있다. Permanent generation은 전체 garbage collection에 포함된다.
GC 동작 과정
위에서 heap이 왜 두 generation으로 나뉘었는지 확인했기 때문에, 정확이 GC 내부에서 어떤 일이 일어나는지 확인해보도록 하자.
다음 그림은 JVM의 객체 할당 과 에이징 프로세스를 보여준다.
Step 1.
가장 먼저, 어떤 새로운 객체가 eden space에 할당된다.
두 survivor space 들은 비어있다.
Step 2.
eden space가 채워지면, minor garbage collection이 수행(triggered) 된다.
Step 3.
참조된 객체는 첫번째 survivor space로 옮겨진다.
참조되지 않은 객체는 eden space가 비워지며 삭제된다.
Step 4.
다음 minor GC에서 eden space에 대해 같은 일이 벌어진다.
참조되지 않은 객체는 삭제되고 참조되는 객체는 survivor space로 옮겨진다.
그러나 이번 경우에는 첫번째가 아닌 두번째 survivor space로 옮겨진다.
게다가, 첫번째 survivor space위의 저번 minor GC에서 나온 객체는 age 상승을 겪고 두번째 survivor space, S1 으로 옮겨지게 된다.
한번 모든 살아있는 객체들이 S1으로 옮겨지게 되면, S0과 eden은 비워지게 된다.
survivor space에 서로 다른 age를 가지는 객체들을 가지고 있다는 것을 기억해두자.
Step 5.
다음 minor GC에서 또 한번 같은 작업이 반복된다.
그러나 이번에는 survivor space가 바뀐다. 참조된 객체들은 S0으로 옮겨진다.
살아남은 객체들은 더 숙성되어지고(aged) Eden과 S1은 비워진다.
Step 6.
이번 슬라이드는 promotion을 묘사한다.
minor GC 이후, 숙성된 객체들이 age 임계치에 다다르면 이 객체들은 young generation에서 old generation으로 승격된다.
Step 7.
마이너 GC가 계속 발생함에 따라 객체는 계속해서 young generation space로 승격된다.
Step 8.
그렇게 해서, 꽤 많은 부분이 young generation에서 다뤄진다.
major GC는 old generation에서 그 공간을 경량화 시키고 치우기 위해 수행될 것이다.
댓글남기기