ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 가비지 컬렉션 GC(Garbage Collection)
    JAVA 2022. 8. 5. 19:20

     

     

     

    GC 란?

    가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스를 말한다.

     

     

    • 장점 
      • C나 C++에서는 이러한 가비지 컬렉션이 없어 프로그래머가 수동으로 메모리 할당과 해제를 일일이 해줘야 한다
      • Java는 JVM에 탑재되어 있는 가비지 컬렉터가 메모리 관리를 대행
      • 개발자 입장에서 메모리 관리, 메모리 누수(Memory Leak) 문제에서 대해 완벽하게 관리하지 않아도 되어 오롯이 개발에만 집중
    • 단점
      • 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없음
      • 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생
        •  GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제 발생 가능
        • 실시간으로 계속 동작해주어야 하는 시스템들(ex)열추적 미사일의 경우)에는 잠깐의 소프트웨어 일시정지로도 목표한 결과가 달라질 수 있기 때문에 GC의 사용이 적합하지 않을 수 있음

     

     

    GC 동작방식

    • Serial GC 방식 : GC를 수행할 때는 GC를 수행하는 스레드 이외의 스레드는 모두 정지(Stop-the-world)
      •  Minor GC
        • young 영역 : 새롭게 생성한 객체들이 위치, 많은 객체가 Young 영역에 생성되었다가 사라짐
        • 대부분의 객체는 금방 unreachable한 상태가 되기 때문에 Minor GC가 많이 발생 (메모리의 부담이 적도록 고안됨)
        • Eden 영역이 가득 참에서 부터 시작
        • Eden 영역에서 참조가 남아있는 객체를 mark하고 survivor0 영역으로 복사
        • Eden 영역 비움 
        • Survivor 영역도 가득차면 같은 방식으로 다른 Survivor1 영역에 복사하고 비움 (2개의 Survivor 영역 중 1개는 반드시 비어있어야 됨)
        • 위의 동작들이 반복되어 특정 횟수만큼 살아남은 객체는 old 영역으로 이동
      • Major GC
        • old 영역 : Young 영역에서 계속 사용되어 살아남은 객체가 복사되는 영역, Young 영역보다 크게 할당, 더 적은 GC 발생
        • 삭제되어야 하는 객체를 mark하고 sweep(지움)
        • Mark-Sweep-Compact 알고리즘

     

    JVM 메모리는 마음대로 조정할 수 없기 때문에 GC 튜닝을 이용한다. GC 튜닝이란  GC 수행시 시스템이 멈추기 때문에 의도치 않은 장애의 원인이 될 수 있어 이를 방지하기 위해 힙 영역을 조정하는 것이다. 

     

     

     

    GC 의 종류  

    • Serial GC
      • GC를 처리하는 쓰레드가 1개 
      • 다른 GC에 비해 stop-the-world 시간이 길다
      • mark-sweep-compact 알고리즘
      • Old영역에서 살아있는 객체를 식별(Mark)하고, 살아있는 객체만을 남김.(Sweep)
      • 이후 객체들을 앞부분부터 채워 객체가 존재하는 부분과 존재하지 않는 부분으로 나눔(Compaction) 
    • Parallel GC
      • Java 8의 default GC
      • Young Generation의 GC를 멀티 쓰레드로 수행
      • Serial GC에 비해 stop-the-world 시간 감소
      • 기본적인 알고리즘은 Serial GC와 같지만 여러 쓰레드를 이용하여 GC를 처리
    • Parallel Old GC(Parallel Compacting GC)
      • Parallel GC를 개선
      • Old Generation에서도 GC를 멀티 쓰레드로 수행
      • Mark-Summary-Compact 알고리즘 사용
      • Serial GC의 Sweep 알고리즘 대신 Summary를 사용
      • Summary 단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아있는 객체를 식별
      • Sweep보다 조금 더 복잡
    • Concurrent Mark & Sweep GC(이하 CMS)
      • Initial Mark 단계에서는 살아 있는 객체를 찾는 것으로 끝 (Stop-the-World 시간이 짧음)
      • compact 과정이 없음
      • initial Mark - GC Root에서 참조하는 객체들만 식별
      • Remark - 이전 단계에서 식별한 객체를 다시 추적, 추가되거나 참조가 끊긴 객체 확정
      • Concurrent Mark - 이전 단계에서 식별한 객체들이 참조하는 모든 객체 추적
        • 찾은 객체에서 참조하는 객체를 Concurrent하게(여러 쓰레드가 동시에) 따라가는 Concurrent Mark 단계가 수행
        • Concurrent하게 Remark가 동작한다
      • 그 이후에 Stop-the-World가 실행
      • Concurrent Sweep - unreachable 객체들을 삭제
      • 애플리케이션의 응답속도가 매우 중요할 때 사용한다.
    • G1(Garbage First) GC
      • CMS GC를 개선
      • Java 9+의 default GC
      • Heap을 일정한 크기의 Region으로 나눔
      • 전체 Heap이 아닌 Region 단위로 탐색
      • compact 진행
      • 바둑판의 각 영역에 객체를 할당하고 GC를 실행
      • 위에서 설명한 Young영역과 Old영역에 대한 개념을 사용하지 않고, 객체를 할당

     

    1.  

     

    GC의 작동 문제의 진단 및 해결

    Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 하는데 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 시스템이 정상적인 상황이 아니라고 생각할 수 있다. 추가적으로, GC에 대한 로그를 확인하여 옵션을 수정할 지 코드를 수정할 지 정해야 한다. 

     

     

    GC에 의한 시스템 중단 시간(Stop-the-world)을 줄이는 법
    • 옵션을 변경하여 GC의 성능을 높이기
      • young 영역과 old 영역의 힙 크기를 높여 GC의 빈도를 줄이는 것
      • 객체의 할당과 promotion을 줄이는 것

    힙의 크기를 높이면, GC 영역이 넓어져 실행시간이 길어지므로 무의미해진다고 생각할 수 있지만 Minor GC의 실행시간은 힙의 크기보다는 살아남은 객체의 수에 의해 더욱 지연되기 때문에 short-lived 객체를 위한 young 영역의 크기를 높인다면 GC의 실행 시간과 호출 빈도를 모두 줄일 수 있다. (하지만 만약 애플리케이션에서 long-lived 객체를 많이 사용한다면, survivor영역으로 복제되는 객체가 많아져 GC에 의한 멈추는 시간이 증가할 수 있다.)

     

    • 설정을 변경하여 GC의 성능을 높이기
      • 애플리케이션을 중단시킨 후에 GC를 병렬로 동시에 진행시키는 것
      • 애플리케이션과 GC작업을 동시에(concurrent) 진행시키는 것

     

     

     

     

    • 개발자의 코드를 변경하여 GC의 성능을 높이기
      • Collection 등을 활용할 때 사용할 객체의 크기를 명시해주기
        • 모든 Java의 Collections와 그를 확장하여 구현한 구현체들(Trove나 Google의 Guava)은 내부적으로 배열을 사용
        • 처음에 설정한 크기를 초과하여 계속 item을 담으려고 하면 내부적으로 새로운 크기의 배열을 생성하고 item을 복사
        • 사용되지 않는 메모리 즉 가비지 증가 
      • 스트림을 바로 사용하기
        • 변경 전: byte[] fileData = readFileToByteArray(new File("myfile.txt"));
        • 변경 후: FileInputStream fis = new FileInputStream(fileName);
        • 예측 불가한 데이터의 크기가 너무 크다면 JVM이 해당 파일의 내용을 할당할 수 없어 OutOfMemoryErrors가 발생할 수 있으며, 할당이 되었다 하더라도 이후에 상당히 큰 규모의 가비지가 될 수 있음 
        • InputStream은 내부적으로 Buffer를 두고 있어 일정한 크기(Chunk)만큼씩 데이터를 조회
        •  InputStream을 사용하면 Buffer를 재사용함으로써 OutOfMemoryErrors를 방지할 수 있고, 가비지의 생성을 최소화
      • String 사용 최적화
        • 중복된 String 생성시 JVM 옵션 활용하기
          • . Java 8u20 업데이트부터는 동일한 문자열에 의해 불필요한 메모리를 사용을 줄이도록 새로운 JVM 파라미터(UseStringDeduplication)를 추가
          • 해당 옵션을 사용시 중복되는 String 인스턴스들을 Global Single Char[]로 관리하여 힙 메모리의 사용을 최적화
          • java -XX:+UseStringDeduplication -jar Application.java
        • StringBuilder의 활용
          • String은 불변 
          • String 연산은 내용 변경이 아닌 새로운 문자열 할당 
          • 불필요한 객체의 생성을 줄이는 방법은 사전에 StringBuilder를 이용하는 것
      • 불변(Immutable) 객체 사용하기
        • 불변의 객체가 먼저 생성되어야 컨테이너가 이 객체를 참조할 수 있. 즉, 컨테이너는 컨테이너가 참조하는 가장 젊은 객체들보다 더 젊다는 것(늦게 생성되었다는 것)
        • GC가 수행될 때, 가비지 컬렉터가 컨테이너 하위의 불변 객체들은 Skip할 수 있도록 도와줌
        • 해당 컨테이너가 살아있다는 것은 하위의 불변 객체들 역시 처음에 할당된 그 상태로 참조되고 있다는 것을 의미하기 때문
      • 불필요한 Collection의 생성을 피하기 
        • Collection 재사용 하기 

     

    불변객체의 예시는 다음과 같다. 

    public class MutableHolder {
      private Object value;
      public Object getValue() { return value; }
      public void setValue(Object o) { value = o; }
    }
    
    public class ImmutableHolder {
      private final Object value;
      public ImmutableHolder(Object o) { value = o; }
      public Object getValue() { return value; }
    }
    • MutableHolder는 계속 다른 값을 참조하여 생존하며 Old 영역으로 이동
    • Old 영역으로 가서도 참조하는 객체가 바뀌기 때문에, Minor GC를 수행할 때 Old 영역으로 와서 MutableHolder까지 검사하여 Young 영역을 정리해야 함 
    • 검사해야 하는 범위가 늘어남 
    •  ImmutableHolder는 참조하는 값이 먼저 존재해야 ImmutableHolder가 존재할 수 있습니다. 
    •  ImmutableHolder가 Old 영역으로 이동하게 되면 MutableHolder Minor GC에 대한 검사를 하지 않아도 됨 
    • 스캔의 범위를 줄여 성능을 높일 수 있음

     

     

     

    (참고한 사이트)

    https://mangkyu.tistory.com/94

     

    [기술면접] CS 기술면접 질문 - 개발 언어 (7/8)

    7. 개발 언어(Java 위주) [ C언어 포인터란? ] 포인터는 어떤 변수의 주소값을 저장하는 변수입니다. 포인터의 선언은 변수명 앞에 *을 붙이면 가능하고, 어떤 변수의 주소값을 얻기 위해서는 &를

    mangkyu.tistory.com

    https://mangkyu.tistory.com/120

     

    [Java] Garbage Collection(가비지 컬렉션)의 성능을 높이는 코딩 방법

    1. Garbage Collection(가비지 컬렉션)의 성능을 높이는 코딩 방법 애플리케이션의 성능은 가비지 컬렉션의 빈도수와 지연 시간에 직접적인 영향을 받기 때문에, 가비지 컬렉션을 최적화하는 것은 상

    mangkyu.tistory.com

    https://www.youtube.com/watch?v=Fe3TVCEJhzo&ab_channel=%EC%9A%B0%EC%95%84%ED%95%9CTech

    https://coding-factory.tistory.com/829

     

    [Java] 가비지 컬렉션(GC, Garbage Collection) 총정리

     가비지 컬렉션(Garbage Collection)이란? 가비지 컬렉션은 영어로 Garbeage Collection으로 줄여서 GC라고도 부릅니다. 가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap 영역에서 동적으로

    coding-factory.tistory.com

     

    'JAVA' 카테고리의 다른 글

    Enum Type ( 열거형 )  (0) 2021.10.26
Designed by Tistory.