Garbage Collection

bo04·2024년 2월 23일
0

Java

목록 보기
2/3

참고

Garbage Collection(GC)

사용하지 않는 메모리(garbage)를 jvm이 자동으로 지워줘서 메모리를 관리해주는 JVM의 GC.
보통 메모리가 부족하거나 cpu가 한가할때 자동으로 실행됨
덕분에 자바 개발자는 훨씬 편리해짐.

  • 예시
		Person person=new Person();
		person.setName("bom"); // 이후에 사용하지 않아서 garbage가 됨
  • 메모리를 다루기 때문에 Heap 영역에서 GC가 사용됨

    Heap 영역의 전제

    • 대부분의 객체는 금방 접근 불가능한 상태(Unreachable)이 됨
    • 오래된 객체에서 새로운 객체로의 참조는 매우 적게 존재함
      → 객체는 대부분 일회성이고 메모리에 오래 남아있는 경우는 드물다
  • jvm이 gc를 실행하는동안 애플리케이션 실행이 멈춰짐. 따라서 이 gc 시간을 줄이는게 gc 튜닝의 핵심!

  • gc를 아무리 해도 더이상 사용 가능한 메모리 영역이 없는데 계속 메모리를 할당하려고 하면 outOfMemoryError가 발생해 WAS가 다운될 수 있음. 따라서 규모있는 자바 애플리케이션을 효율적으로 개발하려면 GC를 잘 알아야됨!

어떤 객체/인스턴스를 GC가 clear 할까?

  • 참조되는 객체란
    1. 힙 내의 다른 객체에 의해 참조될때
    2. 스택 영역이 힙 내의 객체를 참조할때
    3. 네이티브 스택(JNI=Java Native Interface)에 의해 생성된 객체를 참조할떄
    4. 메소드 영역의 정적 변수를 참조할때
  • 그렇다면 참조되지 않는 객체는 위의 4가지를 제외한 경우고 해당 객체들은 GC에 의해 clear됨

Minor GC, Major GC

  • Heap의 물리적인 영역 안
    1. Young Generation
    1. old Generation

young generation

  • 새로운 객체가 할당(Allocation)되는 영역
  • 대부분 객체가 금방 Unreachable 상태가 되기 때문에 여기에 생성되었다가 사라짐
  • 이 영역에 대한 GC=Minor GC

young 영역의 구조

  • eden 1개
    • 새로 할당된 객체가 Allocation 되는 영역
    • 꽉 차면 minor gc가 발생하는데 mark and sweep되고 사용중인 객체는 사용중인 survivor 영역으로 옮겨짐
    • eden 영역에 빠르게 할당하기 위한 기술
      • bump and pointer: eden 영역에 마지막으로 할당된 객체의 주소를 캐싱해둬서 새로운 객체를 할당할때 객체의 크기가 남은 eden 영역의 크기에 적합한지 판별
      • TLABs(Thread-Local Allocation Buffers): 멀티 스레드 환경에서 사용함
  • survivor 2개
    - 최소 한번의 GC 이상 살아남은 객체가 존재하는 영역
    • 사용중인 survivor 영역이 꽉 차면 |mark-sweep되고 사용중인 객체는 비어있는 다른 survivor 영역으로 옮겨짐(survivor 2개 중에서 하나만 사용하고 하나는 비어 있어야 함)
    • 이 반복속에서 살아남은 객체는 old 영역으로 이동(promotion)되는데, 객체의 생존 횟수 카운트를 의미하는 age를 Object Header에 기록하고 minor gc떄 age를 보고 promotion 여부를 결정함

Old Generation

  • young 영역에서 reachable 상태를 유지해 살아남은 객체가 이동(Promotion)되는 영역
  • 너무 큰 객체는 young 영역에 할당되지 않고 처음부터 old 영역에 들어옴
  • mark-sweep-compact됨
  • young 영역보다 크기가 커서 Mainor GC보다 덜 발생하지만 한번 발생하면 old 영역이 너무 커서 시간이 오래 걸림
  • 이 영역에 대한 GC=Major GC=Full GC
  • card table(512 bytes)이 존재
    - old 영역에 있는 객체가 young 영역의 객체를 참조할때 그에 대한 정보를 저장해 놓는 곳
    • 덕분에 minor gc가 동작할때 해당 객체가 참조되고있는지 old 영역까지 검사할 필요 없이, 카드 테이블만 참고하면 돼서 gc 대상 식별 속도가 훨씬 빨라짐

GC의 기본적인 동작 방식

  • young과 old 영역은 메모리 구조가 다르므로 세부적인 동작은 다르지만 공통적인 2가지 단계가 있음
  1. stop the world
    • GC를 하기 위해 gc 실행 전 JVM이 애플리케이션 실행이 멈추는 것을 의미
    • GC를 실행하는 쓰레드를 제외한 모든 쓰레드의 작업이 중단됨
    • 그러므로 이 시간을 줄이면 GC의 성능이 올라감
  2. mark-sweep-compact
    • mark: 사용되는 메모리(객체)와 사용되지 않는 메모리를 식별하는 작업
    • sweep: mark 단계에서 식별된 사용되지 않는 메모리를 해체하는 작업
    • compact: 해체되지 않은 흩어진 객체들을 한쪽으로 몰아 빈 공간을 더 생기게 하는 작업

GC가 언제 실행되야할까? =GC 알고리즘

  • stop the world에 근거해 gc가 실행되는 타이밍에 따라 자바 애플리케이션에 큰 영향이 미침
  • 이 실행 타이밍은 별도의 알고리즘을 기반으로 계산이 되고, 이 계산결과를 기반으로 GC가 실행되는 타이밍이 결정됨
  1. serial GC
    • 옛날 싱글 코어에 사용된 gc로, gc가 하나의 쓰레드에서만 동작하므로 gc에 걸리는 시간이 매우 김
    • minor gc: mark-sweep/ major gc: mark-sweep-compact
  2. parallel GC=throughput GC
    • serial gc와 기본적인 알고리즘은 같지만, 멀티 코어라 gc가 여러 개의 쓰레드로 병렬 처리되기 때문에 gc에 걸리는 시간이 짧아짐
    • 메모리가 충분하고 코어의 갯수가 많을떄 추천되는 알고리즘
  3. parallel old GC=parallel compacting GC
    • parallel GC와 비슷하지만 major GC 알고리즘만 다름
    • major gc:mark-summary-compaction
  4. concurrent mark & sweep GC=CMS GC
    • 매우 복잡한 gc 알고리즘
    • stop the world 시간이 짧은 장점이 있지만 그만큼 다른 gc 알고리즘보다 메모리와 cpu를 더 많이 사용함
  5. G1(garbage first) GC
    • 앞의 young,old 영역 내용하곤 상관없는 알고리즘으로 CMS GC를 대체하기 위해 만들어짐
    • 바둑판에 각 영역에 객체를 할당하고 GC를 실행함. 꽉차면 다른 영역에서 객체를 할당하고 또 GC를 실행함 반복

객체 생성시 GC 성능 올리는 방법

  • trade-off: heap 크기를 적절하게 조절해서 GC의 빈도수와 GC의 실행시간 사이를 적절히 조절하기
  • mark-sweep처럼 객체를 잘 생성해서 최대한 가비지 객체를 덜 만들기
    → 여기서 핵심은 객체 만들때 신경써서 만들자!
  1. Collection의 크기를 예측해서 설정하기

    배열은 immutable이라 초기에 할당되면 수정이 불가능함

    	// 크기 설정 안하고 무작정 만들어놓고 나중에 크기를 초과하는 작업을 하면 따로 새로운 크기의 배열을 생성하고 value를 복사하는 과정을 거쳐야됨
    	// 그럼 기존의 배열은 가비지가 되니 처음부터 적절한 크기를 설정하자
    	List<Integer>  list=new ArrayList<>(3); 
    	list.add(1)
  2. stream 사용하기

    • 파일을 다룰때 파일을 읽어오는 객체를 잘못 만들면 상당히 큰 규모의 가비지가 생길 수 있음
    • 따라서 내부적으로 buffer를 둬서 일정한 크기(chunk)만큼 데이터를 조회하는 stream을 사용하자
  3. string 사용을 주의하자

    string은 immutable임

    • string을 사용할때 +로 합치는 연산을 사용하는 경우가 많아 새로운 String이 내부적으로 만들어지고 기존의 string 객체는 가비지가 되는 경우가 빈번함
    • 그런 경우 String 대신에 StringBuilder를 사용하자
  4. 불변성(Immutability)를 활용하자

    • 불변의 객체인 final를 활용하자
    	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; // final 변수
    	    public ImmutableHolder(Object o) { value = o; }
    	    public Object getValue() { return value; }
    	}
    • 만약 GC가 mark 작업을 하는 동안 ImmutableHolder 객체가 살아있는 객체라고 판별된다면, value는 불변 객체이기 때문에 똑같이 살아있을 수밖에 없어서 gc가 value를 스캔하지 않아 gc 효율이 좋아짐
  5. 불필요한 collection 생성을 피하자

    • 생성하지 말고 기존의 collection을 재사용하려고 하는게 훨씬 메모리 낭비를 방지할 수 있음

0개의 댓글