Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 다익스트라
- 프록시
- Proxy Server
- 호스팅이란?
- 플로이드 워셜
- SSAFY
- 세마포어란?
- 서버 호스팅
- 동기화
- 최단 경로
- 싸피
- 세마포어와 뮤텍스
- 싸피 합격
- 다익스트라 알고리즘
- 프록시서버
- Proxy
- Dijkstra Algorithm
- 플로이드 와샬
- Synchronization
- 뮤텍스
- 클라우드 서버
- 싸피 면접 후기
- floyd-warshall
- 삼성 청년 SW 아카데미
- 호스팅
- 세마포어
- 세마포어와 뮤텍스의 차이
- 웹 호스팅
- 뮤텍스란?
Archives
- Today
- Total
어제의 나보다 성장한 오늘의 나
Java Garbage Collector 본문
가비지 컬렉터란?
- 가비지는 '정리되지 않은 메모리', '유효하지 않은 메모리 주소'를 말한다.
String[] array = new String[2];
array[0] = "0";
array[1] = "1";
array = new String[] {"G", "C"};
- 위 코드에서 String 배열이 할당되지 전에 할당한 0과 1은 어디로 갔을까? 이렇게 주소를 잃어버려서 사용할 수 없는 메모리가 '정리되지 않은 메모리'이다.
- 우리가 선언했던 메모리는 더 이상 접근도 불가능하면서 메모리는 차지한다.
- 프로그래밍 언어에서는 Danling Object, 자바에서는 Garbage라고 부른다.
더보기
자바에서는 JVM의 구성요소중 하나인 Garbage Collection(GC)이 자동으로 사용되지 않는 객체를 해제한다.
- JVM의 메모리는 총 5가지 영역(method, heap, stack, PC, Native method stack)으로 나뉘는데 GC는 Heap 영역만 다룬다.
Stop-the-World란?
- GC를 실행시키기 위해 JVM이 어플리케이션 실행을 멈추는 것이다.
- GC스레드를 제외한 나머지 스레드는 작업을 멈춘다. GC가 끝나야 중단한 작업을 시작한다.
- 어떤 GC알고리즘을 사용하더라도 stop-the-world가 발생한다.
- GC의 튜닝은 Stop the world의 시간을 줄이는 것이다.
weak generational hypothesis
- 자바에서는 개발자가 프로그램 코드에서 명시적으로 메모리를 해제하지 않기 때문에 가비지 컬렉터가 더 이상 필요없는 (쓰레기) 객체를 찾아 지우는 작업을 한다. 가비지 컬렉터는 2가지 가정하에 만들어졌다.
- 대부분 객체는 금방 접근 불가능 상태(unreachable)가 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
- 이러한 가설을 'weak generational hypothesis'라고 한다. 이 가정의 장점을 최대화하기 위해 크게 두 개의 물리적인 공간을 나누었으며, 이를 각각 Young영역, Old 영역이라고 한다.
Reachability
- 어떤 객체를 Garbage로 볼 것이냐? Garbage Collector는 객체를 Reachable과 Unreachable의 상태로 구분한다.
- Unreachable 객체를 가비지로 간주해 GC를 수행한다.
- 한 객체는 여라 다른 객체를 참조하고, 참조된 다른 객체들도 마찬가지로 또 다른 객체들을 참조할 수 있으므로 객체들은 참조 사슬을 이룬다.
- 이런 상황에서 유요한 참조 여부를 파악하려면 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 Root set이라고 한다.
- 런타임 영역은 스레드 영역, 힙 영역(객체를 생성 및 보관), 메서드 영역(클래스 정보가 차지하는 영역) 크게 3가지로 나눌 수 있다.
- 힙 내의 다른 객체에 의한 참조를 제외한 3개가 root set으로, Reachability를 판가름 한다.
- Java 스택, 즉 Java 메서드 실행 시에 사용하는 변수와 파라미터들에 의한 참조
- 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
- 메서드 영역의 정적 변수에 의한 참조
Garbage Collection Algorithms
Reference Counting Algorithm
- 각 객체마다 Reference Count를 관리하여 Reference Count가 0이 되면 GC 수행된다.
- 이 방식은 각 객체마다 Reference Count를 변경해야 되기 때문에 그에 대한 관리비용이 크고 참조를 많이하고 있는 객체의 경우 Reference Count가 0이 될 때 연쇄적으로 GC가 발생할 수 있는 문제점이 있다.
- 위와 같은 구조에서 a로 부터 참조가 끊어졌다 해도 다른 노드로부터의 참조가 남아 있기때문에 Reference Count가 0이 되지 않고, Garbage 대상이 되지 않아 메모리 누수로 이어지는 문제점이 있다.
Mark-and-Sweep Algorithm
- Reference Count Algorithm의 단점을 극복하기 위해 나온 알고리즘이다.
- Root set에서 시작하는 Reference의 관계를 추적하며, Mark Phase와 Sweep Phase로 나뉜다.
- Mark Phase에서는 Garbage 대상이 아닌 살아남아야 할 객체를 Marking하는 방식을 수행한다. Marking을 위해 각 객체의 object hear에 flag나 별도의 bitmap table등을 사용한다.
- Sweep Phase은 Mark Phase가 끝나면 곧바로 실행되며, Marking 정보를 활용하여 Marking되지 않은 객체를 작업한다.
- Sweep Phase이 완료되면 살아남은 모든 객체의 Marking 정보를 초기화한다.
- Before는 GC 발생 전의 상황 Sweep Phase까지 진행된 후에는 Marking 되지 않은 객체들이 지워진다.
- 이 알고리즘은 Reference 관계를 정확히 파악하고 Reference 관계의 변경 시에 별도의 오버헤드가 없어 성능이 비교적 좋기는 하지만, GC가 수행되는 도중 Mark 작업의 정확성과 Memory Corruption을 방지하기 위해 Heap의 사용이 제한되기 때문에 Suspend 현상이 발생한다.
- 그림에서처럼 객체들이 지워진 공간이 fragmentation으로 남게 되어 메모리 할당이 불가능한 상태가 된다.
Mark-and-Compact Algorithm
- 기존 Mark-and-Sweep Algorithm의 fragmentation 문제를 해결하기 위해 제시되었다.
- Sweep 대신 Compact라는 용어를 사용했지만 Sweep이 사라진 것은 아니고 Compact Phase 안에 포함되어 있다.
- 기존의 Sweep 과정까지는 동일하고, 이후에 Compact 과정을 더 거치게 된다.
- Compaction 과정을 거치면 메모리 공간의 효율을 높일 수 있다. 하지만 Compaction 작업 이후 살아남은 모든 객체들의 Reference를 업데이트하는 작업이 필요하기 때문에 부가적인 오버헤드가 발생한다.
Copying Algorithm
- Fragmentation 문제를 해결하기 위해 제시된 또 다른 알고리즘이다.
- 기본 아이디어는 Heap을 Active 영역과 InActive 영역으로 나누어 Active 영역에만 객체를 할당할 수 있게 하고, Active 영역이 꽉 차게 되면 GC를 수행하는 것이다.
- GC를 수행하면 suspend 상태가 되고 살아남은 객체를 InActive 영역으로 복사하는 작업을 수행한다.
- Copy 작업을 완료하면 Active 영역에는 Garbage Object만 남게 되고, InActive 영역에는 살아남은객체들만 남게 된다. 이후 Active 영역을 싹 밀어주면 Free memory 상태가 되고, Active 영역과 InActive 영역이 서로 바뀌게 된다. 이를 Scavenge라고 한다.
- Active 영역과 InActive 영역의 구분은 물리적인 구분이 아니라 논리적인 구분일 뿐이다.
- 복사하는 과정에서, Fragmentation 문제를 해결하기 위해 각 객체들의 Reference를 업데이트하면서 연속된 메모리 공간에 차곡차곡 옮겨준다.
- Fragmentation 방지에는 효과적이지만, 전체 Heap 영역의 절반 정도밖에 사용하지 않는다는 공간 활용의 비효율성, suspend 현상, copy에 대한 오버헤드가 존재한다는 단점이 있다.
Generational Algorithm
- Copying Algorithm을 발전시킨 형태이다.
- Young Generation, Old Generation이라는 영역으로 나누는 알고리즘이다.
- Weak Generational Hypothesis라는 가설을 바탕으로 Young Generation과 Old Generation 이라는 영역으로 Heap 영역을 구분 지었다.
- 객체는 가장 먼저 Young Generation에 할당되고 GC가 수행될 때마다 살아남은 객체에 Age를 기록한다.
- JVM에서는 이 Age의 임곗값의 기본값은 31이라고 한다. 그냥 몇 번 살아남았는지에 대한 정보이다.
- 이 age가 특정 임곗값을 넘어가게 되면 Old Generation으로 Copy하는 작업을 수행한다. 즉, 접근불가능한 상태(**Unreachable)**되지 않아 Young 영역에서 살아 남은 객체가 이 영역으로 복사된다.
- 이를 Promotion이라고 하며, 대부분의 객체는 Young Generation에서 살다가 Garbage가 되기 때문에 Copy 작업의 횟수를 최소화시킬 수 있다.
Garbage Collection 흐름
- JVM이 바로 이 Generational Algorithm을 바탕으로 다음과 같이 Generational Heap을 구성하고 있다.
- jvm에서 heap 영역은 개략적으로 위를 따른다.
- 크게보면 3가지 영역인데 나눠지면 young은 다시 3가지 영역으로 분류된다.
- young 비교적 젊은 reference가 살아있는 곳
- old - 특정 횟수 이상을 살아남은 reference가 살아있는 곳
- permanent - Method Area의 메타정보가 기록된 곳
- eden - young영역중에서도 특히 방금 막 생성된 녀석들이 있는 곳
- survivor 영역이 두개 존재하는데 eden에서 생존된 녀석들이 당분간 생존해 있는 곳
-
- Minor GC - young 영역만 뒤져서 다 죽인다
- Major GC - old 영역까지 뒤져서 다 죽인다. ( 혹은 Full GC )
- Full GC - permanent 영역까지 뒤져서 다 죽인다.이렇게 영역을 나누는 이유는 Full GC를 막기 위해서 이다. Full GC는 모든 heap영역을 몽땅 뒤져서 생존해 있는 녀석들을 모조리 죽여버리는 역활을 한다.
Young 영역
- 2개의 Survivor 영역에 모두 데이터가 존재하거나 모두 사용량이 0이면 시스템은 비정상이다. 즉, 기본적으로 하나만 사용하고 하나는 반드시 비어둔다.
- 먼저 eden영역은 새로 들어오는대로 적재한다. 반드지 eden에 적재된다.
- eden영역이 다차게 됐을 경우 minor GC가 발동된다.
- 각 영역의 처리 절차를 순서에 따라 기술하면 다음과 같다.
- 새로 생성된 대부분의 객체는 Eden 영역에 위치한다.
- Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.
- Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.
- 하나의 Survivor 영역이 가득 차면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그러면 가득 찼던 Survivor 영역에는 아무 데이터도 없는 생태가 된다.
- 이 과정을 반복하다가 계속 살아 남아있는 객체는 Old 영역으로 이동하게 된다.
Old 영역에 있는 객체가 Young 영역의 객체를 참조하는 경우에는 어떻게 처리될까?
- 이러한 경우를 처리하기 위해서 Old 영역에는 512 바이트의 덩어리로 되어 있는 카드 테이블(card table)이 존재한다.
- 카드 테이블에는 Old 영역에 있는 객체가 Young 영역의 객체를 참조할 때마다 정보가 표시된다. Young 영역의 GC를 실행할 때에는 Old 영역에 있는 모든 객체의 참조를 확인하지 않고, 이 카드 테이블만 뒤져서 GC의 대상인지 식별한다.
Old 영역
- Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다.
- Serial GC
- Parallel GC
- G1(Garbage First) GC
Serial GC (-XX:+UseSerialGC)
- Old 영역의 GC는 mark-sweep-compact라는 알고리즘을 사용한다.
- Old 영역에 살아있는 객체를 식별(Mark)
- 힙 영역의 앞부분부터 확인하여 살아있는 것만 남긴다(Sweep).
- 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다(Compaction).
- Serial GC는 적은 메모리와 CPU 코어 개수가 적을 때 적합한 방식이다.
Parallel GC (-XX:+UseParallelGC)
- Parallel GC는 Serial GC와 기본적인 알고리즘은 같다. 그러나 Serial GC는 GC를 처리하는 쓰레드가 하나인 것에 비해, Parallel GC는 GC를 처리하는 쓰레드가 여러 개이다.
- Serial GC보다 빠르게 객체를 처리할 수 있다. Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하다. 이 GC는 Throughput GC라고도 부른다.
G1 GC (-XX:+UseG1GC)
- G1 GC는 바둑판 모양의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다. 즉, 지금까지 Young 영역들에서 Old 영역으로 하는 단계가 사라진 GC 방식이다.
- 하드웨어가 발전하면서 Java 애플리케이션에서 사용할 수 있는 메모리의 크기도 점차 커져갔다. 하지만 기존의 GC 알고리즘들로는 큰 메모리에서 좋은 성능(짧은 stop-the-world)을 내기 힘들었기 때문에 이에 초점을 둔 G1 GC가 등장했다.
- G1 GC의 가장 큰 장점은 좋은 성능이다.
- G1 GC에서의 힙 영역은 Region이라는 특정한 크기로 나눠서 각 Region의 상태에 따라 역할(Eden, Survivor, Old)이 동적으로 부여되는 상태이다. JVM의 힙 영역은 2048개의 Region으로 나뉠 수 있으며, 각 Region의 크기는 1MB ~ 32MB 사이로 지정될 수 있다.
- Humonous - Region 크기의 50%를 초과하는 큰 객체를 저장하기 위한 공간으로, 이 Region에서는 GC 동작이 최적으로 동작하지 않는다.
- Available / Unused - 아직 사용되지 않은 Region을 의미한다.
네이버 개발자의 말씀!
- 만약 애플리케이션에서 만들어지는 모든 객체의 크기와 종류가 같다면 회사에서 사용하는 모든 WAS의 GC 옵션을 동일하게 설정할 수 있다. 하지만, 각 서비스의 WAS에서 생성하는 객체의 크기와 생존 주기가 모두 다르고, 장비의 종류도 다양하다. WAS의 스레드 개수와 장비당 WAS 인스턴스 개수, GC 옵션 등은 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다. JavaOne에서 Oracle JVM을 만드는 엔지니어들이 한 말이다.
출처
Comments