본문 바로가기

Language/Java

Java Garbage Collection









가비지 컬렉션Garbage Collection이란, 시스템에서 더 이상 사용하지 않는 동적 할당된 메모리 블럭 혹은 개체를 찾아 자동적으로 다시 사용 가능한 자원으로 회수하는 것을 말한다. 시스템에서 가비지 컬렉션을 수행하는 부분을 가비지 컬렉터Garbage Collector라고 하며, 최초의 가비지 컬렉터는 1958년에 존 매카시John McCarthy에 의해 리습Lisp 언어의 일부로 구현되었다. 가비지 컬렉션은 약자로 GC라고 부르기도 한다.

일반적인 가비지 컬렉터 알고리즘(Algorithm)은 다음과 같이 동작한다
1. 더 이상 프로그램에서 사용하지 않을 개체Object를 찾아낸다.
2. 해당 개체가 사용하는 리소스를 회수한다.

위치
 : 힙 메모리 영역에 존재(JVM은 New/Young 영역과 Old 영역에 대해서만 GC를 수행

JVM 메모리 구조

역할 : 프로그램 수행 도중 해당 프로그램에서 생성된 객체(인스턴스)를 프로그램이 더 이상 사용하지 않을 때, 그 객체를 메모리에서 해제
장점 : 확보했던 메모리를 해제하지 않으면 계속 누적되어 결국 메모리 오버플로우(Overflow)가 발생하지만 
사용 후의 오브젝트에 대해 신경쓰지 않아도 자동적으로 메모리 해제가 이루어져 프로그래밍에 집중 할 수 있다.

대상 객체
 : 자신의 레퍼런스(주소)를 참조하는 변수 (레퍼런스 변수)가 없으면 가비지 컬렌션의 대상이 된다.
Book b = new Book()
b는 Book이라는 객체의 레퍼런스를 참조하는 레퍼런스 변수로 Book이라는 객체는 자신의 레퍼런스를 참조하는 변수 b가 존재 함으로 가비지 컬렉션의 대상이 될 수 없다.
하지만
Book b = new Book();
b = null;
b 변수에 null 값을 대입함으로 b는 어떠한 레퍼런스도 참조하지 않게 된다.(Book이라는 객체는 버림받게 되고 가비지 콜렉션의 대상이 된다.)

Book b = new Book(); //객체 1
Book c = new Book(); //객체 2
b = c; 
이 경우 Book 객체를 생성하고 b와 c에 객체 레퍼런스를 하나씩 대입하였고 c를 b에 대입하였기 때문에 결국 b는 버림받아 가비지 콜렉션의 대상이 된다.

 C++의 경우 프로그래머가 객체 생성 시 생성자를 호출하며 객체를 메모리에서 해제할 때는 소멸자를 호출하여 메모리 관리
즉, 자바의 경우 가비지 콜렉터가 존재 하기 때문에 소멸자를 호출하지 않아도 된다.
그렇다면 자바의 소멸자는 없는 것인가? 그렇지 않다.
java.lang.Object 클래스에 protected void finalize() throws Thowable 이라는 소멸자가 존재하며 가비지 콜렉터가 이 finalize()메서드를 호출하여 객체를 메모리에서 해제한다.
가비지 콜렉터는 시스템에서 임의로 수행되기 때문에 System.gc()를 호출해도 우선순위를 높일 뿐이지 당장 쓰레기 수집을 하는 것은 아님

protected void finalize() throws Thowable 이란?



Minor GC
New/Young 영역의 GC를 Minor GC라 부른다.
New/Young 영역은 다시 Eden과 Survivor 영역으로 나뉜다.

Eden 영역 : Java 객체가 생성되자 마자 저장되는 곳으로 이렇게 생성된 객체는 Minor GC가 발생할 때 Survivor영역으로 이동된다.
Survivor 영역 : Survivor1, Suvivor2 영역으로 나뉘며 Minor GC가 발생하면 Eden과 Survivor1에 있는 객체를 Survivor2로 복사한다. 그리고 Alive 되어 있지 않은 객체는 자연히 Survivor1에 남겨지고 Survivor1과 Eden 영역을 Clear한다.
이런 방법을 Copy & Scavenge 방식이라 하며 속도가 빠르고 작은 크기의 메모리를 Collecting하는데 효과적이다. Minor GC의 경우 자주 일어나기 때문에 GC에 소요되는 시간이 짧은 알고리즘에 적합(보통 0.5초 이내에 끝남)
Full GC
Old 영역의 GC를 Full GC라 부르며 사용되는 알고리즘으로는 Mark & Compact을 사용한다. 
전체 객체의 Reference를 따라가면서 reference가 연결되지 않은 객체를 Mark한다.
이 작업이 끝나면 사용되지 않는 객체들은(Mark된 객체) 삭제된다.
특징 : 속도가 느리며 Full GC가 일어나는 도중 순간적으로 Java Application이 멈추기 때문에 Full GC가 일어나는 정도와 Full GC에 소요되는 시간은 Application의 성능과 안정성에 아주 큰 영향을 준다.(보통 수초가 걸린다.)


JVM의 일반적인 GC 알고리즘
- Default Collector
- Parallel GC for young generation (from JDK 1.4 )
- Concurrent GC for old generation (from JDK 1.4)
- Incremental GC (Train GC) 

Defalut Collectior 
전통적인 Minor GC에 Scavenge를 Full GC에 Mark & Compact 알고리즘을 사용하는 방법

Parallel GC 
4cpu의 256 정도의 메모리를 가지고 있는 HW에서 유용하게 사용된다.
(1cpu에서는 동시에 여러개의 thread를 실행할 수 없기 때문에 오히려 parallel GC가 Default GC에 비해 느림)

Concurrent GC 
Full GC, 즉 Old 영역을 GC하는 경우 그 시간이 길고 Application이 순간적으로 멈추기 때문에 시스템 운용에 문제가 된다.
그래서 이런 Full GC의 단점을 보완하기 위해 멈추는 현상을 최소화 하는 방법
일부는 Application이 돌아가는 단계에서 수행하고 최소한의 작업만을 Applicaton이 멈췄을때 수행하는 방법으로 Application이 멈추는 시간을 최소화한다.

Incremental GC(Train GC)
Concurent GC와 비슷하며 의도자체는 Full GC에 의해서 Application이 멈추는 시간을 줄이고자 하는데 있다.
동작 원리는 Minor GC가 일어날 때 마다 Old 영역을 조금씩 GC하여 Full GC가 발생하는 횟수나 시간을 줄이는 방법이다.
하지만 Incremental GC는 많은 자원을 소모하고 Minor GC를 자주 일으키고 Full GC가 없어진다거나 획기적으로 줄어드는 것은 아니다. 오히려 느려지는 경우가 있으니 반드시 실환경에 맞게 테스트를 해야한다.