가비지 컬렉션 (Garbage Collection, GC)

    가비지 컬렉션 (Garbage Collection)

    메모리 해제, 자동화, 효율적인 자원 관리

     

    자바는 가비지 컬렉션이라는 자동 메모리 관리 시스템을 제공한다. 개발자가 객체가 더 이상 필요 없을 때 그 객체를 수동으로 메모리에서 해제할 필요가 없다. 즉, 개발자가 명시적으로 메모리를 해제 하지 않더라도, JVM이 주기적으로 가비지 컬렉터를 통해 더 이상 참조 되지 않는 객체를 자동으로 제거하고 메모리를 회수한다. 

     

    STW (Stop The World)

     

    STW는 JVM이 가비지 컬렉션 과정에서 발생하는 이벤트로, 애플리케이션이 일시적으로 멈추는 현상을 의미한다. 가비지 컬렉션을 수행할 때 JVM은 필요한 작업을 완료하기 위해 GC관련 스레드를 제외한 모든 스레드를 일시적으로 중지 시키게 되는데, 이 현상을 STW라고 한다. 따라서, 실시간성이 강조되는 프로그램의 경우 GC가 문제가 될 수 있다.

     

    가비지 컬렉션의 대상

     

    가비지 컬렉션은 참조 되지 않은 상태의 객체를 가비지로 판단하여, 메모리 공간을 회수하여 가용 메모리를 늘린다. 가용 메모리가 일정 크기 이하로 줄어들면 자동으로 가비지를 회수한다.

     

    System.gc();

     

    응용프로그램에서 System 또는 Runtime 객체의 gc() 메소드를 호출하면 가비지 컬렉션을 노출할 수 있다. 하지만 이 문장은 가비지 컬렉션이 필요하다는 요청에 불과하므로 문장 호출 즉시 가비지 컬렉터가 작동하는 것은 아니다. 가비지 컬렉션은 자바 플랫폼이 전적으로 판단하여 적절한 시점에 작동시킨다. 해당 메소드를 호출하는 것은 시스템의 성능에 매우 큰 영향을 미치므로 호출하는 것을 지양해야 한다.

     

    Mark-and-Sweep 알고리즘

     

    가비지 컬렉션은 마크 앤 스윕 알고리즘을 사용하여 힙 영역의 모든 객체를 순회하며 참조 가능한 객체를 마킹(Mark)하고, 마킹되지 않은 객체들을 메모리에서 회수(Sweep)한다.

    STEP 1 : Marking

    가비지 컬렉터는 어떤 메모리 공간이 사용중인지 아닌지를 판별한다. 모든 객체는 마킹 단계에서 스캔하여 이 결정을 내리는데, 모든 객체를 스캔해야하는 경우 매우 많은 시간이 소요된다.

     

    STEP 2 : Sweep

    참조되지 않은 객체를 제거하고 참조된 객체와 빈공간을 가리키는 포인터를 남겨둔다. 

     

    STEP 3 : Compaction

     

    성능을 개선하기 위해 참조되지 않은 객체를 삭제할 뿐 아니라 , 나머지 참조 객체를 압축한다.

     

    Heap 메모리 구조

     

    자바에서 모든 객체는 힙(Heap) 메모리에 할당된다. JVM의 Heap 영역은 2가지 전제를 기반으로 설계되었다.

     

    1 ) 객체는 금방 접근 불가능한(Unreachable) 상태가 된다.

    2) 오래된 객체에서 새로운 객체로의 참조는 거의 존재하지 않는다. 

     

    따라서 효율적인 메모리 관리를 위해 힙메모리는 크게 Young Generation과 Old Generation으로 나뉘어서 저장한다.

     

    📌 Young Generation

     

     새로운 객체들이 할당되는 곳으로, 짧은 수명을 가진 객체가 주로 존재한다. Young Generation에 대한 GC를 Minor GC라고 한다. 대부분의 객체는 생성 후 바로 사라지기 때문에 Young Generation에서 생애 주기가 짧은 객체를 메모리에서 빠르게 제거할 수 있다. GC가 Young 영역에서만 일어나기 때문에 그 영향이 제한적이고 빠르게 처리된다. 

     

    Young Generation은 다시 Eden과 Survivor 공간으로 나뉜다.

     

    Eden은 새로 생성된 객체가 처음 할당 되는 공간이며, Survivor 공간은 Young Generation 내에서 살아남은 객체가 이동되는 공간이며 두개의 영역 (S0, S1)으로 구성되어있다. Eden 영역에 객체가 계속해서 생성되다가 Eden영역이 꽉차게 되면 minor GC가 실행되고, 이때 살아남은 객체는 Survivor-0 영역으로 이동하게 된다. 이러한 과정을 반복하면서 객체들의 age를 증가시키게 되고, age가 임계값에 도달하게 되면 Old Generation으로 이동하게 된다.

     

    일부 객체는 몇 번의 GC 이후에도 살아남을 수 있고, Old Generation으로 바로 이동하기에는 이르다고 판단할 수 있기 때문에 Survivor 공간으로 이동하여 객체가 살아 남는지 여부를 평가하기 위해 사용한다.

     

    Old Generation이 더 많은 메모리를 차지하고 관리가 복잡하기 때문에 바로 Old Generation으로 이동하게 되면 불필요한 메모리를 많이 차지 할 수 있으며, OG에서의 Full GC는 성능에 더 큰 영향을 미치기 때문에 불필요하게 많은 객체를 OG에 할당하는 것은 바람직하지 않다. 

     

    따라서 Survivor 공간을 통해 객체가 충분히 오래 살아남을 경우에만 Old Generation으로 이동시킴으로써, Old Generation의 크기를 최소화하고 불필요한 Full GC를 방지할 수 있다.

     

    📌 Old Generation

     

    오랜 시간 살아남은 객체들이 이 곳에 존재하게 되며, Old Generation의 메모리가 부족하게 되면 Major GC 또는 Full GC가 발생한다.

    OG는 Young 영역보다 크게 할당되고, GC가 적은 빈도수로 발생하지만 시간이 많이 소요되는 작업이다. (Young영역의 생존기간이 짧은 객체는 큰 공간이 필요 없으며 큰 객체가 Old 영역에 할당되므로 시간이 많이 소요됨!)

     

    📌 Permanent Generation

     

    Permanent Generation는 클래스 정보(메타 데이터)를 저장하는 공간으로, 항상 고정된 크기를 가지고 생성된다. 오라클에서 가져온 사진에서는 Permgen이 heap의 일부분처럼 되어있는데, PermGen은 JVM의 힙 외부에 존재하는 메모리 영역이다. (매우 헷갈렸다..오라클 핫스팟JVM 기준으로 메서드 영역을 PermGen이라고 정의하는 것 같다.,)

     

    https://stackoverflow.com/questions/41358895/permgen-is-part-of-heap-or-not

     

    Permgen is part of heap or not?

    I have found picture from official oracle site but in popular SO answer I have found that permanent generation is not part of heap Permanent Generation (non-heap): The pool containing all the

    stackoverflow.com

    위의 스택오버플로우에서도 Permgen은 Non-heap이라고 설명되어 있다.

     

     

    저장되는 정보들 

    • JVM이 클래스나 메서드를 로드할 때 해당 클래스에 대한 메타데이터(클래스 이름, 메서드 정보, 메모리 참조 정보 등)
    • 클래스에 선언된 정적 필드 및 메세드 정보도 저장
    • 클래스에 정의된 상수들
    • JVM이 필요로하는 내부 메타데이터

     

    문제점

    • 고정된 크기로 인해 JVM의 클래스 로딩 및 메타데이터 저장이 많아지면 메모리 부족문제를 일으킬 수 있으며, 이 문제를 해결하려면 PermGen크기를 수동으로 늘려야 한다.
      • -XX:PermSize와 -XX:MaxPermSize로 초기 크기와 최대 크기를 설정할 수 있다.
      • OutOfMemoryError: PermGen 영역의 크기가 부족하면 java.lang.OutOfMemoryError: PermGen space 오류가 발생한다.

     

    📌 Metaspace

     

    따라서 Java 8부터 Permanent Generation이 사라진 대신, Metaspace가 추가되었다. (Java8부터는 Permgen관련 옵션은 무시한다.) 중요한 것은 Heap영역이 아닌 Native 메모리 영역에 위치한다는 것이다.

    Permanet Generation과 달리 제한된 크기를 가지고 있지 않으며, 필요한 만큼 OS가 자동으로 조정한다.

    클래스 메타데이터는 객체처럼 동적으로 생성되고 삭제되는 것이 아니라 클래스가 로드될 때 한번 생성되고, 사용하지 않으면 언로드된다. 하지만 힙 영역은 객체의 생성과 삭제가 주기적으로 이루어지기 때문에 GC가 자주 개입하여 메모리 관리를 수행하게 되고, 클래스 메타데이터는 객체와 달리 상대적으로 고정적이고 클래스가 언로드 될 때 한 번에 메모리를 해제하기 때문에 metaspace같은 native 메모리 영역에서 관리하는 것이 적합할 것이다.

     

    참고 자료

    댓글