참고
Garbage Collection(GC)
사용하지 않는 메모리(garbage)를 jvm이 자동으로 지워줘서 메모리를 관리해주는 JVM의 GC.
보통 메모리가 부족하거나 cpu가 한가할때 자동으로 실행됨
덕분에 자바 개발자는 훨씬 편리해짐.
Person person=new Person();
person.setName("bom");
-
메모리를 다루기 때문에 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
- 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가지 단계가 있음
- stop the world
- GC를 하기 위해 gc 실행 전 JVM이 애플리케이션 실행이 멈추는 것을 의미
- GC를 실행하는 쓰레드를 제외한 모든 쓰레드의 작업이 중단됨
- 그러므로 이 시간을 줄이면 GC의 성능이 올라감
- mark-sweep-compact
- mark: 사용되는 메모리(객체)와 사용되지 않는 메모리를 식별하는 작업
- sweep: mark 단계에서 식별된 사용되지 않는 메모리를 해체하는 작업
- compact: 해체되지 않은 흩어진 객체들을 한쪽으로 몰아 빈 공간을 더 생기게 하는 작업
GC가 언제 실행되야할까? =GC 알고리즘
- stop the world에 근거해 gc가 실행되는 타이밍에 따라 자바 애플리케이션에 큰 영향이 미침
- 이 실행 타이밍은 별도의 알고리즘을 기반으로 계산이 되고, 이 계산결과를 기반으로 GC가 실행되는 타이밍이 결정됨
- serial GC
- 옛날 싱글 코어에 사용된 gc로, gc가 하나의 쓰레드에서만 동작하므로 gc에 걸리는 시간이 매우 김
- minor gc: mark-sweep/ major gc: mark-sweep-compact
- parallel GC=throughput GC
- serial gc와 기본적인 알고리즘은 같지만, 멀티 코어라 gc가 여러 개의 쓰레드로 병렬 처리되기 때문에 gc에 걸리는 시간이 짧아짐
- 메모리가 충분하고 코어의 갯수가 많을떄 추천되는 알고리즘
- parallel old GC=parallel compacting GC
- parallel GC와 비슷하지만 major GC 알고리즘만 다름
- major gc:mark-summary-compaction
- concurrent mark & sweep GC=CMS GC
- 매우 복잡한 gc 알고리즘
- stop the world 시간이 짧은 장점이 있지만 그만큼 다른 gc 알고리즘보다 메모리와 cpu를 더 많이 사용함
- G1(garbage first) GC
- 앞의 young,old 영역 내용하곤 상관없는 알고리즘으로 CMS GC를 대체하기 위해 만들어짐
- 바둑판에 각 영역에 객체를 할당하고 GC를 실행함. 꽉차면 다른 영역에서 객체를 할당하고 또 GC를 실행함 반복
객체 생성시 GC 성능 올리는 방법
- trade-off: heap 크기를 적절하게 조절해서 GC의 빈도수와 GC의 실행시간 사이를 적절히 조절하기
- mark-sweep처럼 객체를 잘 생성해서 최대한 가비지 객체를 덜 만들기
→ 여기서 핵심은 객체 만들때 신경써서 만들자!
-
Collection의 크기를 예측해서 설정하기
배열은 immutable이라 초기에 할당되면 수정이 불가능함
List<Integer> list=new ArrayList<>(3);
list.add(1)
-
stream 사용하기
- 파일을 다룰때 파일을 읽어오는 객체를 잘못 만들면 상당히 큰 규모의 가비지가 생길 수 있음
- 따라서 내부적으로 buffer를 둬서 일정한 크기(chunk)만큼 데이터를 조회하는 stream을 사용하자
-
string 사용을 주의하자
string은 immutable임
- string을 사용할때
+
로 합치는 연산을 사용하는 경우가 많아 새로운 String이 내부적으로 만들어지고 기존의 string 객체는 가비지가 되는 경우가 빈번함
- 그런 경우 String 대신에
StringBuilder
를 사용하자
-
불변성(Immutability)를 활용하자
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; }
}
- 만약 GC가 mark 작업을 하는 동안
ImmutableHolder
객체가 살아있는 객체라고 판별된다면, value
는 불변 객체이기 때문에 똑같이 살아있을 수밖에 없어서 gc가 value를 스캔하지 않아 gc 효율이 좋아짐
-
불필요한 collection 생성을 피하자
- 생성하지 말고 기존의 collection을 재사용하려고 하는게 훨씬 메모리 낭비를 방지할 수 있음