ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 가비지 컬렉션 (Garbage collection)
    프로그래밍/Java 2020. 10. 19. 16:30
    반응형

    Q. Garbage collection란?

    가비지(Garbage): 더이상 의미없는 데이터

    컬렉션(collection): 더이상 의미없는 데이터를 정리한다.

    즉, 가비지 컬렉션은 더 이상 의미 없는 데이터의 정리, 메모리를 정리 한다는 의미이다. 

    Garbage collection은 JVM 힙 메모리 정리 알고리즘이다. JVM은 힙 메모리에 객체를 생성한다. 힙 메모리 내에서 더 이상 사용되지 않는 객체는 새로운 객체로 재할당될 수 있도록 정리시키는 과정이다. 일반적으로 객체가 다른 객체에 의해 더 이상 참조되지 않을 때, 그 객체는 정리 대상이다. 

     

    Q. Garbage collector란?

    말 그대로 Garbage collection을 수행하는 담당자이다.

    JVM에는 Garbage collection 알고리즘을 수행하는 Garbage collector가 있다. 힙 메모리에 객체가 더이상 필요없게 되면 JVM의 Garbage collector가 객체를 정리한다. 

     

    Q. 객체가 더 이상 참조되지 않는다는 것은 어떻게 알까? 

    가비지 컬렉터는 루트 객체(활동 상태이고 프로세스가 사용 중인 객체)로부터 시작해 모든 참조를 찾아다닌다. 참조의 기준은 Reachability를 본다. 어떤 객체가 유효한 참조를 가지고 있다면 Reachable한 객체로 판단되어 정리되지 않는다. 반면, 참조를 가지고 있지 않다면 Unreachable한 객체로 판단되어 더 이상 사용될 수도 없고, 이는 정리된다. 아래 그림과 같이 진하게 표시된 객체는 Unreachable한 객체이며 가비지 컬렉션이 수행될 때 정리 대상이 된다. 이 객체를 담고 있던 메모리는 재사용될 수 있다. 

     

     

    Q. [심화] Garbage collection의 과정은?

    Garbage collection의 기본적인 알고리즘은 Mark(탐색) and Sweep(정리) 알고리즘이다. 여기서 성능을 위해 Compact(조각모음) 이 추가될 수 있다.

    Garbage collection은 힙 메모리를 탐색하는 과정부터 시작해서 객체들이 사용되고 있는지 아닌지 확인하고 사용되지 않는 객체는 정리한다. 

    Step 1: Marking

    첫번째 과정은 마킹이라고 부른다. Garbage collector가 메모리의 조각들이 사용되고 있는지 아닌지 확인하는 단계이다. 

    참조된 객체들은 하늘색이다. 참조되지 않은 객체는 금색이다. 모든 객체는 정리를 결정하기 위해서 확인되며 이 작업은 모든 객체들이 탐색되어야할 때는 시간이 매우 걸린다. 

    Step 2. Normal Deletion 

    이 단계에서 참조되지 않은 객체들을 제거하고 참조된 객체와 남은 공간에 대한 포인터를 유지한다. Memory allocator는 새로운 객체가 할당될 수 있는 여유 메모리 공간에 대한 덩어리에 대한 포인터를 들고 있는다. 

    Step 2a. Deletion with Compaction

    성능 향상을 위해서 사용되지 않는 객체를 제거할 뿐만아니라 남아있는 공간에 대한 조각모음을 할 수도 있다. 참조된 객체들을 한 곳으로 옮김으로써 새로운 메모리 할당을 더 쉽고 빠르게 만들 수 있다.

    JVM 힙 메모리에 왜 Generation을 붙였나? 

    Mark and Sweep 처럼 할당된 모든 객체들을 마킹하고 조각모음을 하는 것은 JVM에 있어서는 비효율적이다. 객체가 많이 할당될수록 객체가 더 오래 살아있을수록 GC 시간은 오래걸리게 된다. 하지만 조사결과에 따르면 앱에서 보여주는 행태는 대부분의 객체는 생존해있는 시간이 짧다는 것이다.

     

    JVM Generation

    위의 교훈에서 알 수 있듯이 힙 메모리가 사용되는 방식으로 JVM의 성능을 향상시킬 수 있다. 그러므로 힙 메모리는 3개의 generation으로 쪼갤 수 있다. Young, Old, Permanent Generation 이다.

    Young generation은 모든 새로운 객체가 할당되고 나이를 먹는 구간이다. Young Generation 은 또다시 Eden, Survivor Space 0, 1 로 세분화 되어진다. 이 구역이 객체로 다 차면, minor gc가 일어난다. Minor gc는 Stop the world를 발생시킵니다. 객체가 사라질 가능성이 높다면, 빠르게 정리되겠지만, 살아남은 객체들은 나이를 먹고 결국엔 Old generation 구간으로 옮겨진다. Young Generation에서만 GC가 자주 발생되도록 한다. 

    "Stop the world"란? 

    Stop the world란 GC가 이루어지는 동안 모든 프로세스와 스레드가 잠시 동작을 멈춘다. 심지어 Minor GC 조차도 Stop the world 를 일으킨다.(oracle 공식문서 확인, All minor garbage collections are "Stop the World" events. This means that all application threads are stopped until the operation completes. Minor garbage collections are always Stop the World events.) GC 쓰레드를 돌리기 위해서 JVM의 앱을 중지시키는 것이다. 가비지 컬렉터는 전체 힙을 모두 방문하면서 정리 대상이 될 객체를 찾는다. 

    Old generation은 오래 살아남은 객체를 저장한다. 일반적으로 Young generation에 대한 임계치가 설정되어 있어서 그 나이가 되면 이 구역으로 옮겨진다. 이 구역이 꽉차면 Major gc가 일어난다.

    Major gc도 역시 stop the wolrd가 일어나고 종종 major gc는 전부 살아있는 객체가 있을 수 있기 때문에 매우 느려진다. 때문에 앱들의 경우 Stop the world에 걸리는 시간은 이 Major gc에 의해 좌지우지 된다고 해도 과언이 아니다. 

    마지막으로 Permanent generation은 JVM이 클래스와 메소드에 대한 메타데이터를 저장하도록 하는 영역이다. 앱의 생명주기 동안 사용중인 클래스를 JVM에 의해 채워지고 Java 라이브러리 클래스 및 메소드가 이 구역에 저장될 수 있다. 또한 JVM이 클래스가 더 이상 필요하지 않음을 발견하면 Full gc가 수행될 수 있다. (단, Java8 부터는 Permanent generation 영역이 Metaspace 영역으로 대체됨) 힙 영역보다는 메소드 영역에 적합한 것 같다.

     

     

    Q. Stop the world 를 줄이려면 어떻게 해야하는가?

    왜 줄여야하지?

    앱이 메모리를 작게 사용하는 앱의 경우 25ms 정도 밖에 걸리지 않는데, 기기가 좀 더 강력해지고 앱의 크기가 커지면서 가비지 컬렉션에 걸리는 시간도 점점 늘어날 수 있다. 이러한 시스템의 멈춤이 안드로이드의 UI 동작을 방해하기 때문에 Stop the world는 적게, 그리고 짧게 일어날 수록 좋다. 

    어떻게 줄여야하지?

    위에서 설명했듯이 Old generation에서 major GC가 GC의 시간을 좌지우지 하기 때문에 Old generation으로 넘어가는 객체를 줄여야한다. 

    참조되지 않는 객체를 오래 들고 있지 않도록, 즉 GC가 빠르게 일어나도록 약한 참조로 만든다.

    참조의 종류에는 StrongReference(강한 참조) WeakReference(약한 참조) 있다. StrongReference 객체를 생성하려면 new 생성하고 WeakReference 객체를 생성하려면 WeakReference class 사용한다. 이 WeakReference 객체는 GC가 특별하게 취급한다. 이 WeakReference에 참조된 객체는 루트 객체가 참조하더라도 unreachable한 상태로 판단해 GC에 포함시킨다. 

    예를 들면 안드로이드의 경우 워커 쓰레드가 Activity의 생명주기보다 길 수가 있는데 이를 대비해 워커 쓰레드가 Activity를 오래 들고 있지 않도록 약한 참조로 들고 있도록 한다. 

     

     

     

     

    Q. Java 앱 개발자가 GC를 왜 공부해야할까?

    사용 가능한 메모리 영역이 가득 찼을 때, OOM(Out of memory)이 발생하거나 힙 메모리의 객체를 모두 탐색하는 Full GC가 수행이 되는데 이 때 3~4초간 앱이 멈추게 된다. 앱의 성능을 생각하는 개발자라면 GC가 어떻게 처리되는지, 어떻게 하면 Full GC가 일어나지 않게 하는지 기본지식을 알고 있어야한다.

     

     

    가산점) 가비지 컬렉터 원리

    안드로이드 가비지 컬렉터의 변화

    진저브레드 전에는 stop the world가 호출되면 UI 쓰레드가 멈춰서 사용자의 경험을 방해했다.

    진저브레드 부터는 부분적인 정리를 수행하는 concurrent GC가 도입된다. 부분적인 GC는 전체 힙을 다 조사하지 않아서 참조되지 않은 모든 개체를 정리하지 못하지만 속도는 더 빠르다. concurrent GC는 앱을 멈추는 대신 앱과 동시에 구동된다. 시스템은 GC가 시작하고 끝날 때만 5ms 씩만 멈춘다. 시스템이 더 짧게 멈추게 되어 "stop the world" 방식이 사라지면서 앱은 GC와 동시에 구동할 수 있었다.

    킷캣이나 그 이전 기기에서 가비지 컬렉션은 단순하게 "mark and sweep" 방식이다. 오래된 객체는 발견되어 제거되지만 나머지 객체는 그대로 남아있다. 아래 그림처럼 첫 번째 행, 두 번째 행을 보면 이 방식이 어떻게 동작하는지 알 수 있다. GC가 구동되면 할당된 메모리 영역에서 참조되지 않은 객체들이 제거되어 할당되어 있던 공간의 중간마다 여유 메모리 영역 조각이 생긴다. 힙 크기가 작은 기기나 작은 크기의 메모리 가비지 컬렉션이 많이 이루어진 경우에는 이미 사용 중인 메모리와 여유 메모리가 뒤섞여 파편화가 발생한다. 20MB의 메모리가 비어 있는 것으로 나타나지만 실제 사용 가능한 가장 큰 메모리의 조각은 1MB일 수도 있다. 이런 경우 4MB를 할당받으려고 하면 연속된 4MB를 찾지 못해서 메모리 부족 오류가 발생할 수 있다. 

    롤리팝부터 Dalvik대신 ART 런타임이 사용되기 시작하면서 가비지 컬렉션이 또 개선됐다. ART의 모토인 "가비지 컬렉션이 방해가 아니라 도움이되어야 한다"를 지키기 위해 시스템이 멈추는 횟수는 두 번에서 한 번으로 줄어들었고 GC의 횟수도 줄어 들게 되었다. 실제 GC에 걸리던 시간도 10ms에서 3ms 정도로 더 짧아졌다. 더욱이 ART가 사용된 후부터는 비트맵과 같은 큰 객체도 메모리 관리를 단순하게 하려고 자체적인 힙에 할당받게 되었다. 

    ART에 적용된 새로운 GC알고리즘은 많지만 그 중에 흥미로운 알고리즘이 있다. 앱이 포그라운드에 표시되지 않을 때 수행되는 "Semi-Space GC"이다. 앱이 포그라운드에 표시 되지 않을 때, 메모리 내에 오브젝트를 재배열하는 방법이 가장 안전하다. 위에 그림의 두번째 행처럼 중간에 빈틈이 많았던 메모리를 여유 메모리 영역에 복사해서 빈틈 없이 연속되도록 재배치해서 세 번째 행처럼 만드는 것이다. 이렇게 하면 앱이 더 큰 여유 메모리 영역을 할당받을 수 있다. 메모리에서 객체가 이동하게 되면, 앱은 오류를 피하려고 잠시 멈춰야한다. 이 과정에서 쟁크가 발생할 수 있으므로 앱이 포그라운드에 표시되지 않을 때만 Semi-Space GC가 작동한다. 사실 이건 완전한 압축 GC는 아니지만 미사용 메모리에 연속된 큰 영역을 만들어주는 데 매우 유용한 방법이다. 

    최신 버전에서 지원하는 GC 압축 방식 : 최신 안드로이드에서는 압축 가비지 컬렉터가 사용된다. 그러므로 이제는 메모리의 조각 모음을 하고, 여유 메모리를 늘리려고 메모리의 위치를 이동시킬 때 발생하는 여러 문제를 해결해줄 수 있다. 압축 GC는 Semi-Space GC보다 한 단계 더 나간다. 새로운 메모리 영역으로 복사하는 것 대신 현재 있던 메모리 위치를 유지시키면서도 파편화를 없앤다. 

     

     

    참조 - [책] 안드로이드 앱 성능 최적화 

    https://ui.toast.com/weekly-pick/ko_20200228/ 

    yaboong.github.io/java/2018/06/09/java-garbage-collection/ 

    https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

    wiserloner.tistory.com/554

    반응형

    '프로그래밍 > Java' 카테고리의 다른 글

    보안 소켓  (0) 2021.04.04
    URL과 URI  (1) 2021.02.15
    함수형 프로그래밍 설계 -1  (0) 2017.06.29
    Java Collection  (0) 2015.03.21
    오버로딩(method overloading) vs 오버라이딩(overriding)  (0) 2014.07.30
Designed by Tistory.