모든 자바 애플리케이션은 JVM(Java Virtual Machine
) 위에서 작동한다. 따라서, JVM
이 작동하는데 있어서 메모리 구조와 GC
는 애플리케이션의 응답 시간과 성능에 밀접한 관계를 맺고 있다.
GC란 무엇인가?
- GC(Garbage Collection)는 자바 애플리케이션에서 사용하지 않는 메모리를 자동으로 수거하는 기능을 말한다.
C/C++
같은 언어는 메모리를 할당하고 직접 해제해야했지만, 자바에서는GC
를 이용하여 개발자들이 메모리 관리를 비교적 신경쓰지 않아도 된다.
JVM 메모리 영역
GC
의 동작 방법을 이해하려면, 먼저 자바의 메모리 구조를 이해할 필요가 있다.- 일반적으로 애플리케이션에서 사용되는 객체는 오래 유지되는 객체보다 잠시 사용되는 경우가 많다.
- 자바에서는 크게 두 영역으로 메모리를 구분한다.
Young
영역과Old
영역인데,Young
영역은 생성된지 얼마 되지 않은 객체들을 저장하는 장소이고Old
영역은 생성된지 오래된 객체를 저장하는 장소이다.
영역 | 설명 |
---|---|
New/Young 영역 | 이 영역은 자바 객체가 생성되자마자 저장되고 생긴지 얼마 안 된 객체가 저장되는 곳이다. 자바 객체가 생성되면 이 영역에서 저장되다가 시간이 지남에 따라서 우선 순위가 낮아지면 Old 영역으로 옮겨진다. |
Old 영역 | New/Young 영역에서 저장되었던 객체 중에 오래된 객체가 이동되어서 저장되는 영역이다. |
Perm 영역 | Class, Method 등의 코드가 저장되는 영역으로, JVM에 의해서 사용된다. |
JVM이 메모리를 관리하는 방식
Minor GC
- 먼저
New/Young
영역을Minor GC
라고 부른다.New/Young
영역은Eden / Survivor
이라는 두 영역으로 또 나뉘게 된다. Eden
영역은 자바 객체가 생성되자마자 저장되는 곳이다. 이렇게 생성된 객체는Minor GC
가 발생할 때Survivor
영역으로 이동하게 된다.Survivor
영역은Survivor1
과Survivor2
두 영역으로 나뉘는데,Minor GC
가 발생하면Eden
과Survivor1
에 활성 객체를Survivor2
로 복사한다.- 활성이 아닌 객체는 자연스럽게
Survivor1
에 남아있게 되고,Survivor1
과Eden
영역을 클리어 한다. (결과적으로 활성 객체만Survivor2
)로 이동하게 된 것이다. - 다음번
Minor GC
가 발생하면 같은 원리로Eden
과Survivor2
영역에서 활성 객체를Survivor1
으로 이동시키게 된다. 계속 이런 방식을 반복하면서Minor GC
를 수행한다. - 이렇게
Minor GC
를 수행하다가Survivor
영역에서 오래된 객체는Old
영역으로 옮기게 된다. - 이러한 방식의
GC
알고리즘을Copy & Scavenge
라고 한다. 이 방식은 속도가 매우 빠르며 작은 크기의 메모리를 콜렉팅하는데 매우 효과적이다. Minor GC
의 경우에는 자주 일어나기 때문에GC
에 걸리는 시간이 짧은 알고리즘을이 적합하다.
FULL GC
Old
영역의 가비지 컬렉션을Full GC
라고 부르며Full GC
에 사용되는 알고리즘을Mark & Compact
라고 한다.Mark & Compact
알고리즘은 객체들의 참조를 확인하면서 참조가 연결되지 않은 객체를 표시한다. 이 작업이 끝나면 사용되지 않는 객체를 모두 표시하고 이 표시된 객체를 삭제한다.Full GC
는 속도가 매우 느리며,Full GC
가 일어나는 도중에는 순간적으로 자바 애플리케이션이 멈춰버리기 때문에,Full GC
가 일어나는 정도와 시간은 애플리케이션의 성능과 안정성에 아주 큰 영향을 미친다.
GC가 중요한 이유
- 가비지 컬렉션 중에서 마이너 GC의 경우에는 보통 0.5 이내에 끝나기 때문에 큰 문제가 되지 않지만, 그러나 FULL GC의 경우에는 자바 애플리케이션이 멈춰 버리기 때문에, 문제가 될 수 있다.
- 멈추는 동안 사용자의 요청이 큐에 들어있다가, 순간적으로 요청이 한꺼번에 들어오기 때문에 과부하에 의한 여러 장애를 만들 수 있다.
- 따라서 원활한 서비스를 위해서는
GC
가 어떻게 일어나게 하느냐가 시스템의 안정성과 성능에 큰 변수로 작용할 수 있다.
다양한 GC 알고리즘
앞에서 설명한 방식 말고도 다양한 GC
방법을 제공하고 있다. 방식은 다음과 같다.
- Default Collector
- Parallel GC for young generator
- Concurrent GC for old generator
- Incremental GC (Train GC)
Default Collector
- 앞에서 설명했던 전통적인 GC 방식으로, Minor GC로
Scanvenge
를 Full GC로Mark & Compact
알고리즘을 사용한다.
Parallel GC
-
자바는 멀티 스레드 환경을 지원하지만, 하나의 CPU에서는 동시에 하나의 스레드 밖에 수행할 수 없어서 예전에는 하나의 CPU에서만 GC를 수행하였지만 후에 하나의 CPU에서 동시에 여러 개의 스레드를 실행할 수 있는 하이퍼스레딩 기술이나 여러개의 CPU를 동시에 장착한 하드웨어의 보급으로 하나의 하드웨어에서 동시에 여러 개의 스레드를 수행할 수 있게 되었다.
-
Parallel GC
에는 크게 두 가지 종류의 옵션을 가지고 있는데,Low-pause
방식과Throughput
방식이 있다. -
Low-pause
방식은 GC가 일어날 때 애플리케이션이 멈추는 현상을 최소화하는데 역점을 두었다. -
Throughput
방식의Parallel
GC는 마이너 GC가 발생하였을 때 되도록이면 신속하게 수행하도록throughput
에 중점을 두었다.
Concurrent GC
-
Full GC 즉, Old 영역을 GC하는데 시간이 길고 애플리케이션이 순간적으로 멈춰버리므로, 애플리케이션이 멈추는 현상을 최소화 하는 GC 방식이다.
-
Full GC에 소요되는 작업을 애플리케이션을 멈추고 진행하는 것이 아니라, 일부는 애플리케이션이 돌아가는 단계에서 수행하고 최소한의 작업만을 애플리케이션이 멈췄을 때 수행하는 방식이다.
Incremental GC (Train GC)
-
Train GC
라고도 불리는GC
방식은 의도는Full GC
에서 의해서 애플리케이션이 멈추는 시간을 줄이기 위한 것이다. -
작동은
Minor GC
가 일어날 때마다Old
영역을 조금씩GC
해서Full GC
가 발생하는 횟수나 시간을 줄이는 방식이다. -
Incremental GC
는 많은 자원을 소모하고Minor GC
를 자주 일으켜서, 그리고Incremental GC
를 사용한다고Full GC
가 없어지거나 그 횟수가 획기적으로 줄어드는 것이 아니다. 오히려 느려지는 경우가 많으므로 반드시 테스트를 거치고 사용해야한다.
GC 로그 수집 방법
JVM
에서는 GC 상황에 대한 로그를 남기고자 옵션을 제공하고 있다.- 자바 옵션에
-verbosegc
라는 옵션을 주면 되고,>
리다이렉션 명령어를 통해서 파일로 저장하고 분석할 수 있다.
- 위는 스프링 부트로 만든 간단한 웹 애플리케이션이다. 이를 터미널에서
jar
파일로 만들어 준다.
./gradlew bootjar
정상적으로 생성이 되면, 다음과 같은 경로에 jar
파일이 생성된다. 터미널을 통해서 이 경로로 이동한다.
그런 후에 다음과 같은 명령어를 터미널에서 실행 시킨다.
java -jar -verbosegc app.jar
- 그러면 애플리케이션이 실행되자마자
GC
와 관련된 로그들이 출력된다. - 마이너 GC는 “GC"로 표기되고, FULL GC는 “FULL GC"로 표기된다.
- 그 다음의 값은
HEAP SIZE BEFORE GC
인데, GC전의 힙 사용량 (New/Young 영역 + Old 영역 + Perm 영역의) 크기를 나타낸다. HEAP SIZE AFTER GC
는 GC가 발생한 후의 HEAP 사용량이다. 마이너 GC가 발생하였을 때는Eden
과Survivor
영역을GC
하게 되므로HEAP SIZE AFTER GC
는 Old 영역의 용량과 유사하다.- 마지막 값은
GC
에 소요된 시간을 나타낸다.
JVM GC 튜닝
STEP 1. 애플리케이션의 종류 및 튜닝 목표값을 설정
- JVM 튜닝을 할 때 가장 중요한 것은 튜닝의 목표를 설정하는 것이다.
- 메모리를 적게 사용하는 것이 목표인지,
GC
횟수를 줄이는 것이 목표인지,GC
에 걸리는 시간이 문제인지, 애플리케이션의 성능(Throughput or Response Time
) 향상이 목표인지를 먼저 정하고 나서 목표치에 근접하도록JVM
파라미터를 조정하는 것이 필요하다.
STEP 2. 힙크기와 Perm
크기 설정
-
-ms, -mx 옵션을 이용해서 힙 크기를 정한다. 일반적으로 서버 애플리케이션은 ms와 mx크기를 같게 하는 것이 메모리의
Growing
와Shrinking
에 의한 불필요한 로드를 막을 수 있다. -
ms
와mx
크기를 다르게 하는 경우는 애플리케이션의 시간대별 메모리 사용량이 급격하게 변화가 있는 애플리케이션에 효과적이다.
STEP 3. 테스트와 로그 분석
JVM
옵션에GC
로그를 수집하기 위한-verbosegc
옵션을 적용한다.nGrinder
와 같은 스트레스 테스트 도구로 애플리케이션에 스트레스를 주어서, 그 로그를 수집한다.- 튜닝에 있어서 가장 중요한 것은 목표 산정이지만, 그만큼이나 중요한 것은 실제 튜닝한 파라미터가 애플리케이션에 어떤 영향을 주는지를 테스트 하는 것이다.
STEP 4. Perm 크기 조정
STEP 5. GC 수행 시간 분석
-
Full GC가 일어나는 횟수가 많아서
Old
영역을 늘려주면, Full GC 가 일어나는 횟수가 줄어들 것이고, 반대로 Full GC 수행 시간은 늘어날 것이다. -
특히 서버 애플리케이션은 Full GC가 일어날 때는
JVM
자체가 멈춰버리기 때문에 일정 시간동안 응답을 못하는 상태가 된다. -
서버 애플리케이션에서 Full GC가 적게 일어나게 하고, Full GC 시간을 양쪽 다 줄이려면
Old
영역의 메모리를 줄이고 여러 개의 인스턴스를 동시에 띄워서 로드 밸런싱을 해주면 부하가 분산된다. -
그렇게 되면,
Full GC
가 일어나는 횟수가 줄어들게 되며Old
영역을 줄였기 때문에 Full GC가 수행되는 시간 또한 줄어든다. -
그리고 하나의 서버 인스턴스가 멈춰있는 동안 로드 밸런싱이 되는 다른 서버가 응답하고 있기 때문에
Full GC
로 인하여 애플리케이션이 멈추는 상황에서 받을 영향을 최소화 할 수 있다.
STEP 6. 파라미터 변경
- 각 영역의 허용 범위를 기준으로,
Old
영역과New
영역을 적절하게 조절한다. Perm
크기와New
영역의 배분(Eden, Survivor) 영역을 조정한다.- 가장 중요한 것은
Old
영역과New
영역의 비율을 어떻게 조정하는가이다.
향상 포인트 | GC 알고리즘 |
---|---|
Perfomance (속도) | Parallel GC |
Responsiveness (응답성) | Concurrent GC |
Responsiveness (응답성) | Incremental GC |
일반 | Default GC |
참고 문헌
>> Home