본문 바로가기

Programming/Java

[Java] 가비지 컬렉션(Garbage Collection)

여기저기서 나오는 메모리, 자바에서 말하는 가비지, GC 정리하자!

 

 

가비지 컬렉션(Garbage Collection) 정의

 

  1.  가비지(Garbage)

 

    -  정리되지 않은 메모리, 유효하지 않은 메모리 주소

 

    -  주소를 잃어버려 사용할 수 없는 메모리

 

    -  앞으로 사용하지 않고, 메모리를 가지고 있는 객체도 포함

 

    -  프로그래밍 언어에서Danling Object / Java에서 Garbage라고 부른다.

 

public class Main {

    public static void main(String[] args) {

        int[] arr = new int[3];
        arr[0] = 0;
        arr[1] = 1;
        arr[2] = 2;

        arr = new int[] {8, 9, 10};
    }
}

 

위의 부분에서, arr 이라는 배열이 생성 및 초기화 되어 index가 0, 1, 2인 원소에 값이 각각 0, 1, 2가 할당 되었으나 

 

그 아래에서 arr라는 int형 배열이 다시 생성되면서, 8, 9, 10이라는 값이 새로 할당되었다.

 

이때, 새로 값을 할당 하기 전에 할당한 0, 1, 2의 값이 주소를 잃어버려서 사용할 수 없는 메모리가 된, Garbage라는 것이다.

 

 

  2.  가비지 컬렉션(Garbage Collection)

 

    -  메모리 관리 방법 중 하나로,

 

    -  시스템에서 더 이상 사용하지 않는, 동적 할당된 메모리 블럭을 찾아 

 

       자동으로, 다시 사용 가능한 자원으로 회수하는 것

 

   -> 즉, 더 이상 필요 없어진 메모리인 가비지(Garbage)를 효과적으로 처리하는 작업을 GC(Garbage Collection)이라고 한다.

 

 

   3.  가비지 컬렉터(Garbage Collector)

 

    -  가비지 컬렉션을 수행하는 부분

 

 

 

Garbage Collector 목적 및 작동 원리

 

  1.  메모리 할당

 

  2.  사용중인 메모리 인식

 

  3.  사용하지 않는 메모리 인식  

 

 

Garbage Collection 동작 방식

 

  -  Young 영역과 Old 영역의 구조는 다르지만, 공통적으로 두 가지 단계를 거친다.

 

 

  1.  Stop The World

 

    1)  정의

 

      -  'Stop The World'는 가비지 컬렉션 실행을 위해 JVM이 애플리케이션 실행을 멈추는 것

 

      -  Stop The World 발생 시 GC를 실행하는 쓰레드를 제외한 모든 쓰레드의 작업 중단

 

     -> 모든 쓰레드의 작업이 중단 -> 애플리케이션 Stop 되므로 이렇게 Stop 되는 시간을 줄여야 한다.

 

    2)  GC Tuning(GC 튜닝)

 

      -  애플리케이션 Stop, 즉 Stop The World의 발생 시간을 줄이기 위한, GC 성능 개선 작업

 

      -  JVM에서도 GC 튜닝을 위한 다양한 실행 옵션 제공

 

  2.  Mark and Sweep

 

    1)  Mark

 

    -  사용되는 메모리와 사용되지 않는 메모리를 식별

 

    -  가비지 컬렉터가 닿을 수 있는(Reachable) 스택의 변수나 객체를 스캔하며, 어떤 각각이 어떤 객체를 가리키는지 찾는 과정

 

  -> 사용되고 있는 메모리 식별

 

   -> 이 과정에서, Stop The World 발생

 

 

    2)  Sweep

 

    -  Mark 단계에서 사용되지 않음으로 식별된 메모리 해제

 

  → 즉, Mark -> Stop-The-World -> Sweep 의 단계로 가비지 컬렉션이 실행된다고 할 수 있다. 

 

 

JVM Heap 영역

 

  -  JVM의 메모리는 클래스 영역 / Java Stack / Heap / Native Method Stack  의 4개 영역으로 구분된다.

 

  -  Garbage Collector는 JVM의 메모리 중 Heap 메모리를 다룬다.

 

  -  JVM 의 Heap은 Yong / Old / Permanent 영역으로 구분된다.

 

JVM의 Heap Memory Structure

    1.  Young 영역

 

     -  새롭게 생성한 객체가 위치하는 영역

 

     -  대부분의 객체는 짧은 시간에 unreachable 상태가 되어 -> 많은 객체가 Young 영역에 생성되었다가 사라진다.

 

   →  Young 영역에서 발생한 GC = Minor GC

 

 

    2.  Old 영역

 

     -  Young 영역에서 Reachable 상태를 유지해 살아남은 객체가 복사되는 영역

 

       + ) 이때, Reachable 상태란, Stack에서 Heap 영역의 객체에 대해 참조할 수 있는 상태를 의미한다.

 

            즉, 어떤 객체에 대해 유효한 참조가 있는 상태이다.

 

     -  대부분, Young 영역보다 크게 할당

 

     -  크기가 큰 만큼, Young 영역보다 GC가 적게 발생

 

 

    3.  Permanent 영역

 

     -  Method Area로, 클래스와 메소드 정보와 같이 Java 언어 영역 수준에서는 거의 사용되지 않는 영역

 

   →  Young 영역을 제외한 나머지 두 영역에서 발생한 GC = Major GC(or Full GC)

 

 

JVM Heap 영역에 따른 Garbage Collection 동작 방식

 

  -  Young 영역에서 발생한 GC인 Minor GC와

 

      Young 영역을 제외한 나머지 두 영역에서 발생한 GC인 Major GC 각각의 동작 방식에 대해 알아보면, 

 

 

  1.  Minor GC

 

     (1)  Young 영역 구조

 

       -  1개의 Eden 영역 + 2개의 Survivor 영역 -> 총 3가지

 

        1)  Eden 영역 : 새로 생성된 객체가 할당(Allocation)되는 영역

 

        2)  Survivor 영역 : 최소 1번 GC 이상 살아남은 객체가 존재하는 영역

 

     (2)  Young 영역의 동작 순서

 

GC 전과 후에 따른 Young 영역 동작 순서

 

 

        1)  새로 생성된 객체가 Eden 영역에 할당된다.

 

        2)  객체가 계속 생성되어 Eden 영역이 꽉 차게 되고 -> Minor GC 실행

 

          -  Eden 영역에서 사용되지 않는 객체의 메모리가 해제된다.

 

          -  Eden 영역에서 살아남은 객체는 1개의 Survivor 영역으로 이동한다.

 

        3)  1~2번의 과정이 반복 -> Survivor 영역이 가득 차게 되면,

 

         -> Survivor 영역의 살아남은 객체를 다른 Survivor 영역으로 이동 시킨다.

 

          -  이때, 1개의 Survivor 영역은 반드시, ' 빈 상태 '가 된다.

 

         -> 만약, 두 Survivor 영역 모두에 데이터가 존재하거나,

 

             두 영역 모두 사용량이 0 이라면, 정상적인 시스템 상황이 아니라고 판단할 수 있다. 

 

        4)  위의 1~3번의 과정을 반복하여, 계속해서 살아남은 객체는 -> ' Old 역역 ' 으로 이동(Promotion) 된다.

 

     (3)  객체의 생존 횟수

 

       -  객체의 생존 횟수를 Count 하기 위해 Minor GC에서 객체가 살아남은 횟수를 의미하는 ' age ' 를 ' Object Header ' 에 기록한다.

 

     ->  Minor GC 때 Object Header에 기록된 age를 통해 -> Promotion 여부를 결정

 

 

     (4)  HotSpot JVM

      

       -  JVM(Java Virtual Machine)의 성능 향상 기반인 두 축으로,

 

          GC(Gabage Collection)과 HotSpot 엔진의 성능 향상이 존재한다.   

 

       1)  HotSpot JVM의 정의

 

        -  Hot 한 Spot을 찾아 해당 부분에서 JIT Compiler 를 사용하는 JVM

 

        -  ' HotSpot '이란, Sun Microsystem사의 Java 엔진 이름으로, 

 

        -  Java VM의 엔진으로는, HotSpot(Sun/Oracle), J9(IBM), JRocket(Oracle) 가 기업용 자바 환경에서 주로 사용된다.

               + JRocket: 서버 쪽 성능 개선에 집중

 

               + HotSpot 은 오픈소스 Java 프로젝트인, OpenJDK의 JVM 엔진(맥을 쓰면서, OpenJDK를 현재 사용중이다.)

 

      

       2)  Java HotSpot 구현 종류

 

        -  Client / Server 두 가지 (Java SE 안에 포함 / Java 시작 시 모드 선택 종류)

 

        -  Client : 스타트업 시간 과 메모리 공간에 대한 최적화 중점

 

        -  Server : 시작 시간은 조금 소요되더라도, 다수의 Request를 빠르게 처리하는 데에 중점

 

       3)  Java HotSpot Client Compiler 

 

        -  클라이언트 모드에서 동작하는 컴파일러 - 프로그램 시작시간 최소화에 집중

 

        -  클라이언트 모드의 세 단계

 

           (1단계)  bytecode 해석 시 HIR (High-level Instruction Representation, 상위 중간 코드)이라고 하는 정적인 바잌트코드 표현을 만듦.

 

                 ->  최적화 용이하게 함. 

 

           (2단계)  HIR로부터 플랫폼에 종속적인 중간표현식 LIR (Low-level Instruction Representation, 하위 중간 코드) 을 만듦.

 

           (3단계)  LIR을 사용해 기계어 생성

 

        -  클라이언트 모드의 JIT의 특징은, [바이트 코드로부터 -> 최대한 많은 정보를 뽑아내 -> 실제 동작하는 코드 블럭에 대해 최적화를 집중] 하는 것

 

       -> 즉, 클라이언트 모드의 JIT는 전체적인 최적화에 큰 관심이 없는 것이다. 

 

 

       4)  Java HotSpot Server Compiler 

 

        -  서버 모드의 JIT Compiler는 부분적인 코드 실행보다 << 전체적인 성능 최적화에 집중한다.

 

        -  서버 모드의 단계

 

            (1단계)  일반적인 컴파일러 기술을 이용해 -> 일단, 코드 최적화

 

                       죽은 코드 삭제(Dead Code Elimination)

 

                       Loop 변수 끌어올리기(Loop Invariants Hoisting)

 

                       공통 부분식 제거(Common Subexpression Elimination)

 

                       상수 지연(Constant Propagation)

 

                       전역 코드 이동(Clobal Code Motion)

 

            (2단계)  Java에 최적화된 최적화 수행

 

                       Null Check 삭제

 

                       배열의 Range Check 삭제

 

                       예외처리 경로 최적화

 

                       대단위 RICS(Reduced Instruction Set Computer) 레지스터들을 최대한 활용하기 위해, Graph 연산을 통한 Register 할당

 

       →  위와 같은 과정을 통해 상대적으로, 느린 속도로 JIT Compile이 수행되지만, ' 코드의 수행 ' 은 더욱 빠르다.

 

 

       5)  Hotspot VM에서 메모리 할당을 빠르게 할 수 있도록 도와주는 기술

 

        -  bump-the-pointer

 

bump the pointer 기술

 

 

        -  TLAB(Thread-Local Allocation Buffers)

 

 

 

        -  PLAB(Parallel-Local Allocation Buffers)

 

 

 

  + ) [OS] CISC와 RISC 다른 게시물에 추가 정리

 

 

 

  2.  Major GC(Full GC) 

 

    (1)  Old 영역(Old Generation) 구조

 

      -  Young 영역에서 Tenuring Threshold(임계치) 보다 오래 살아남는 객체들이 넘어오는(Promotio 되는) 영역

 

      -  객체들이 Young 영역에서 계속해서 Promotion 되어 Old 영역의 메모리가 부족해지는 경우 발생

 

      -  이 Old 영역에서 일어나는 GC를 Major GC(or Full GC)라고 한다.

 

      -  이때,  JVM이 GC 쓰레드를 제외하고 모든 애플리케이션 영역을 일시적으로 멈추는 현상인 STW (Stop The World)가 발생한다. 

 

      -  Young 영역 크기 << Old 영역 크기 -> GC에 0.5초 ~ 1초 소요 -> Minor GC는 애플리케이션에 영향이 크지 X

 

      -  But, Old 영역 >> Young 영역 /  Young 영역을 참조 가능 -> Major GC는 Minor GC 보다 오랜 시간 소요(10배 이상)

 

 

 

Reachability (Reachable / Unreachable)

 

  1.  Reachability

 

    -  위에서 반복해서 말했지만, Gabage Collection은 Gabage 객체인지, GC를 수행할 그 대상을 식별해야한다.

 

    -  이 식별을 위해 [Reachability]의 개념을 사용한다.

 

    -  즉, 유효한 메모리인지 아닌지를 판별하는데,

 

       어떤 객체에 '유효한 참조' 가 있으면 -> 'Reachable' 객체로

 

       어떤 객체에 대한 참조가 '유효하지 않은' 참조이면 -> 'Unreachable' 객체로 구분하고,

 

   →  'Unreachable' 객체 = Garbage로 판단한다.

 

    -  그렇다면, 객체에 대한 Reachability 를 판단한다는 것은 이 판단에 어떤 조정이나, 제어를 할 수 있다는 것이다.

 

       (이러한 부분은 전혀 생각해보지도 못했다. 사실만을 그대로 받아들이는 데에 익숙해지지 말고, 비판적으로 수용하 고, 직접. 의도나 영향을 생각해보자.)

 

   →  즉!! 코드 작성 과정에서 GC에 관여가 가능하다는 것이고, Java에서 이를 위해 java.lang.ref 패키지에 SoftReference, WeakReference 등을 제공한단다.

 

 

  2.  Reference

 

    -  기존적으로, [new]로 할당되는 메모리 = 모두 [Strong Reference] 를 가짐 

 

   →  캐시 생성 시 메모리 누수에 주의 필요

 

    -  원래 데이터에서 - 캐시의 키가 삭제 -> 캐시 내부의 '키 - 값'은 Garbage가 된다.

 

    -  그러나, GC는 '삭제된 캐시의 키 = Garbage' 를 인식하지 X

 

       왜냐하면, Garbage가 되기 전 캐시에 추가된 데이터가 Strong Reference로 -> 독자적인 Reachability를 가지기 때문.

 

   →  따라서, 캐시에 데이터 추가 시 처음부터, Weak Reference로 넣어준다면 ? 

 

   →  Weak Reference는 new로 할당된 객체의 유효 참조를 '인위적'으로 설정 가능하게 함.

 

   →  원래 데이터 삭제 시 [이 객체에 Weak Reference가 걸려있는 객체] = [Garbage]로 인식.

 

   ※  따라서, 캐시 생성 시 WeakHashMap 사용 권장

 

  + )  Java 참조 유형 4가지

 

https://lion-king.tistory.com/entry/Java-%EC%B0%B8%EC%A1%B0-%EC%9C%A0%ED%98%95-Strong-Reference-Soft-Reference-Weak-Reference-Phantom-References

 

 

Garbage Collection의 한계

 

  -  어떤 방식을 적용하든, 실행 시간에 GC 작업이 수행되는 한, 성능 하락은 불가피

 

  -  Garbage Collection 수행 시 더 이상 접근 불가능한 객체, 즉 Unreachable 객체만 회수하여 메모리 누수 발생 가능

 

 → 이 문제는, 위에서 언급한 Weak Reference 적용을 통해 어느 정도 제어가 가능하다. 

 

 

 

 

 

 

 

 

 

 

 

다음은,, Gabage Collection의 알고리즘 종류이다.. 

공부할 게 너무 많다 진짜 너무 많다.

치열하게 살자 제발!!!!!!!!!!!!!!

 

 

 

참고

 -  https://s2choco.tistory.com/14

 -  https://m.blog.naver.com/2feelus/220738480797

 -  https://d2.naver.com/helloworld/1329

 -  https://velog.io/@litien/%EA%B0%80%EB%B9%84%EC%A7%80-%EC%BB%AC%EB%A0%89%ED%84%B0GC

 -  https://blog.metafor.kr/163

 -  https://mangkyu.tistory.com/118