티스토리 뷰

자바 메모리 구조

 

 

클래스 영역(메소드 영역, 코드 영역, 스태틱 영역)  : 클래스 정보 , 변수 정보, 메소드 정보(메서드 이름, 리턴타입, 매개변수, 접근제어자), 스태틱 변수 등을 저장

 

스택 영역 : 메서드 호출시마다 메서드 호출을 위한 스택 프레임 생성, 메서드 안에서 사용되는 지역변수, 매개변수, 리턴값 등등을 저장

 

힙 :  new 연산자로 생성된 객체 인스턴스, 배열 등을 저장

 

가비지 컬렉터를 통해 할당된 메모리 반환

 

Native 메소드 : 자바 외 다른 언어에서 제공되는 메소드들이 저장되는 공간

 

PC 레지스터 : Thread가 생성될 때 생성되는 공간, Thread가 어떤 부분을 어떤 명령으로 실행할 지에 대한 기록, 현재 실행되는 부분의 명령과 주소를 저장

 

가비지 컬렉션 의미

 

 

위 그림에서 new int[3]은 array라는 변수에 String 배열을 집어넣은 이후 사용할 수 없음

 

참조가 없기 때문

 

그래서 new int[3]은 가비지가 되고, garbage collection은 이러한 힙영역 내의 참조가 없는 쓰레기 객체들을 청소하는 프로그램을 의미

 

Java의 가비지 컬렉터는 크게 2가지 작업을 수행

 

  1. 힙(heap)내의 객체 중에서 가비지(garbage)를 찾아낸다.

  2. 찾아낸 가비지를 처리해서 힙의 메모리를 회수한다.

 

GC와 Reachability

 

  • Java GC는 객체가 가비지인지 판별하기 위해서 reachability라는 개념을 사용함.

  • 어떤 객체에 유효한 참조가 있으면 reachable, 없으면 unreachable 로 구별

  • unreachable 객체를 가비지로 간주해 GC를 수행.

  • 유효한 참조 여부를 파악하기 위해서는 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 root set이라고 한다.

HotSpot JVM의 런타임 데이터 영역

 

 

런타임 데이터 영역은 위와 같이 스레드가 차지하는 영역들과, 객체를 생성 및 보관하는 하나의 큰 힙, 클래스 정보가 차지하는 영역인 메서드 영역, 크게 세 부분으로 나눌 수 있다.

 

힙에 있는 객체들에 대한 참조는 다음 4가지 종류 중 하나이다.

 

  • 힙 내의 다른 객체에 의한 참조

  • Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조

  • 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조

  • 메서드 영역의 정적 변수에 의한 참조

 

이들 중 힙 내의 다른 객체에 의한 참조를 제외한 나머지 3개가 root set으로, reachability를 판가름하는 기준이 된다.

 

 

root set으로부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고, 이 참조 사슬과 무관한 객체들이 unreachable 객체로 GC 대상이다. 오른쪽 아래 객체처럼 reachable 객체를 참조하더라도, 다른 reachable 객체가 이 객체를 참조하지 않는다면 이 객체는 unreachable 객체이다.

 

객체가 GC대상이 되는 경우

 

일반적으로

 

  • 모든 객체 참조가 null일 경우 (object = null)

  • 객체가 블럭 안에서 생성되고 블럭이 종료되었을 경우

  • 부모 객체가 null이 되었을 경우, 자식 또는 포함된 객체들은 자동적으로 GC 대상이 된다.

 

stop-the-world

 

의미 : GC를 실행하기 위해 JVM이 애플리케이션 실행을 멈추는 것

 

stop-the-world가 발생하면 GC를 실행하는 쓰레드를 제외한 나머지 쓰레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작

 

가비지 컬렉션 성능 → 자바 애플리케이션 성능

 

 

heap의 각 영역

 

가비지 컬렉터는 두 가지 가설 하에 만들어짐

 

  1. 대부분의 객체는 금방 접근 불가능 상태가 된다.

  2. 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.

 

이 가설의 장점을 최대한 살리기 위해서 HotSpot VM에서는 크게 2개로 물리적 공간을 나누었음. Young 영역과 Old 영역

 

Young 영역 : 새롭게 생성한 객체의 대부분이 여기에 위치. 대부분의 객체가 금방 접근 불가능 상태가 되기 때문에 매우 많은 객체가 Young 영역에 생성되었다가 사라진다. 이 영역에서 객체가 사라질 때 Minor GC가 발생한다고 말함

 

--> 실제로 시스템이 정상적으로 동작하고 설정을 제대로 했다면 발생되는 GC의 대부분은 Minor GC

 

Old 영역 : 접근 불가능 상태로 되지 않아 Young 영역에서 살아남은 객체가 여기로 복사된다. 대부분 Young 영역보다 크게 할당하며, 크기가 큰 만큼 Young 영역보다 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC(Full GC)가 발생한다.

 

--> 크기가 크기 때문에 Minor GC보다 GC 시간이 오래 걸림

 

Young 영역의 구성

 

Young 영역은 3개의 영역으로 나뉜다.

 

Eden 영역

Survivor 영역(2개)

 

 

 

각 영역의 처리 절차는 다음과 같다.

 

새로 생성한 대부분의 객체는 Eden 영역에 위치한다.

Eden 영역에서 GC가 한 번 발생한 후 살아남은 객체는 Survivor 영역 중 하나로 이동된다.

Eden 영역에서 GC가 발생하면 이미 살아남은 객체가 존재하는 Survivor 영역으로 객체가 계속 쌓인다.

하나의 Survivor 영역이 가득 차게 되면 그 중에서 살아남은 객체를 다른 Survivor 영역으로 이동한다. 그리고 가득 찬 Survivor 영역은 아무 데이터도 없는 상태로 된다.

이 과정을 반복하다가 계속해서 살아남은 객체는 Old 영역으로 이동하게 된다.

 

이 절차를 봤을 때 Survivor 영역 중 하나는 반드시 비어 있는 상태로 남아 있어야 함. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 시스템이 정상적인 상황이 아니라고 생각하면 된다.

 

Old 영역에 대한 GC

 

Old 영역은 기본적으로 데이터가 가득 차면 GC를 실행한다. GC 방식에 따라서 처리 절차가 달라짐.

 

Serial GC

Parallel GC

Parallel Old GC(Parallel Compacting GC)

Concurrent Mark & Sweep GC(CMS방식)

G1(Garbage First) GC

 

Serial GC (-XX:+UseSerialGC)

 

Young 영역에서의 GC는 앞 절에서 설명한 방식을 사용. Old 영역의 GC는

mark-sweep-compact이라는 알고리즘을 사용. 이 알고리즘의 첫 단계는 Old 영역에 살아 있는 객체를 식별하는 것(Mark). 그 다음에는 힙(heap)의 앞 부분부터 확인하여 살아 있는 것만 남긴다(Sweep). 마지막 단계에서는 각 객체들이 연속되게 쌓이도록 힙의 가장 앞 부분부터 채워서 객체가 존재하는 부분과 객체가 없는 부분으로 나눈다.(Compaction).

 

Parallel GC(-XX:+UseParallelGC)

 

Parallel GC는 Serial GC와 기본적인 알고리즘은 같지다. 그러나 Serial GC는 GC를 처리하는 스레드가 하나인 것에 비해, Parallel GC는 GC를 처리하는 쓰레드가 여러 개다. 그렇기 때문에 Serial GC보다 빠른게 객체를 처리할 수 있다. Parallel GC는 메모리가 충분하고 코어의 개수가 많을 때 유리하다. Parallel GC는 Throughput GC라고도 부른다.

 

다음 그림은 Serial GC와 Parallel GC의 스레드를 비교한 그림이다.

 

 

Parallel Old GC(-XX:+UseParallelOldGC)

 

이 방식은 앞서 설명한 Parallel GC와 비교하여 Old 영역의 GC 알고리즘만 다르다. 이 방식은 Mark-Summary-Compaction 단계를 거친다. summary 단계는 앞서 GC를 수행한 영역에 대해서 별도로 살아 있는 객체를 식별한다는 점에서 Mark-Sweep-Compaction 알고리즘의 Sweep 단계와 다르며, 약간 더 복잡한 단계를 거친다.

 

CMS GC(-XX:+UseConcMarkSweepGC)

 

CMS방식은 지금까지 설명한 GC 방식보다 더 복잡하다.

 

 

 

초기 Initial Mark 단계에서는 클래스 로더에서 가장 가까운 객체 중 살아 있는 객체만 찾는 것으로 끝낸다. 따라서, 멈추는 시간은 매우 짧다. 그리고 Concurrent Mark 단계에서는 방금 살아있다고 확인한 객체에서 참조하고 있는 객체들을 따라가면서 확인한다. 이 단계의 특징은 다른 스레드가 실행 중인 상태에서 동시에 진행된다는 것이다.

 

그 다음 Remark 단계에서는 Concurrent Mark 단계에서 새로 추가되거나 참조가 끊긴 객체를 확인한다. 마지막으로 Concurrent Sweep 단계에서는 쓰레기를 정리하는 작업을 실행한다. 이 작업도 다른 스레드가 실행되고 있는 상황에서 진행한다.

 

이러한 단계로 진행되는 GC 방식이기 때문에 stop-the-world 시간이 매우 짧다. 모든 애플리케이션의 응답 속도가 매우 중요할 때 CMS GC를 사용하며, Low Latency GC라고도 부른다.

 

그런데 CMS GC는 stop-the-world 시간이 짧다는 장점에 반해 다음과 같은 단점이 존재한다.

 

다른 GC 방식보다 메모리와 CPU를 더 많이 사용한다.

Compaction 단계가 기본적으로 제공되지 않는다.

 

따라서, CMS GC를 사용할 때에는 신중히 검토한 후에 사용해야 한다. 그리고 조각난 메모리가 많아 Compaction 작업을 실행하면 다른 GC 방식의 stop-the-world 시간보다 stop-the-world 시간이 더 길기 때문에 Compaction 작업이 얼마나 자주, 오랫동안 수행되는지 확인해야 한다.

 

G1 GC

 

G1 GC는 바둑판의 각 영역에 객체를 할당하고 GC를 실행한다. 그러다가, 해당 영역이 꽉 차면 다른 영역에서 객체를 할당하고 GC를 실행한다. 즉, 지금까지 설명한 Young의 세 가지 영역에서 데이터가 Old 영역으로 이동하는 단계가 사라진 GC 방식이라고 이해하면 된다. G1 GC는 장기적으로 말도 많고 탈도 많은 CMS GC를 대체하기 위해서 만들어 졌다.

 

 

G1 GC의 가장 큰 장점은 성능이다. 지금까지 설명한 어떤 GC 방식보다도 빠르다. 하지만, JDK 6에서는 G1 GC를 early access라고 부르며 그냥 시험삼아 사용할 수만 있도록 한다. 그리고 JDK 7에서 정식으로 G1 GC를 포함하여 제공한다. Java8에서는 기본 방식으로 적용되었다.

 

그리고 어떤 서비스에서 A라는 GC 옵션을 적용해서 잘 동작한다고 그 GC 옵션이 다른 서비스에서도 훌륭하게 적용되어 최적의 효과를 볼 수 있다고 생각하지 말라는 것이다.

 

WAS의 스레드 개수와 장비당 WAS 인스턴스 개수, GC 옵션 등은 지속적인 튜닝과 모니터링을 통해서 해당 서비스에 가장 적합한 값을 찾아야 한다.

 

 

동작 방식 : heap 영역이 기본적으로 연속된 구간에 동일한 크기의 여러 세트로 나뉘어 있음. 세트들은 이전 가비지 콜렉터와 같은 역할들을 할당받으나 그 크기는 정해지지 않는다. 각 영역의 크기가 정해지지 않았기 때문에 메모리 영역의 사용에 대한 유연성을 제공

 

G1 방식은 CMS 콜렉터와 비슷하게 동작함. G1은 heap 메모리에서 객체가 살아있는지 결정하는 동시적이고 전역적인 마킹을 수행한다.

 

마킹 단계가 끝난 뒤에는 어떤 지역이 가장 많이 비어 있는지 알 수 있게 된다. 빈 공간이 많은 지역부터 메모리 회수를 시작하고, 그렇기 때문에 이름이 Garbage-First이다.

 

G1에 의해 교체를 해야하는 지역으로 확인된 곳은 배출에 의해 가비지 콜렉션을 당합니다. G1은 하나 이상의 지역으로부터 object를 카피하여 하나의 지역에 넣습니다. 그 과정에서 메모리를 비우고 최적화를 합니다.

 

이전과 동일하게 eden, survivor, old 영역은 있으나 permanent 영역은 사라졌다.

heap 영역을 작은 단위로 쪼개어 각 지역은 eden, survivor, old 영역으로 사용 가능하다. 이 것이 뜻하는 것은 각 영역의 크기가 가변적이라서 효율적인 활용을 뜻한다.

병렬적으로 각 영역을 마킹하고 회수하기 때문에 stop-the-world 시간이 줄고 처리 속도가 늘어났다.

 

G1 GC 특징

 

GC의 대상 영역이 여러개의 region으로 나뉘어 있기 때문에 GC 가 일어나면 전체 heap 에 대해서 GC를 하지 않고, 일부 region 에서만 GC를 수행

Young, Old 영역에 대해서 따로 region이 나뉘어지지 않고 같이 사용된다.

 

Young

 

Young 영역의 GC는 parallel GC이다.

 

Old

 

Old 영역의 GC는 Old 영역의 일부 region만 GC가 일어난다. 이 때 Young 영역의 GC도 같이 일어날 수 있다.

GC 하는 방식은 CMS와 유사하다.

Old 영역의 GC는 필요에 따라서 모든 application의 thread를 멈추고 GC thread와 동시에 실행될 수 있다.

CMS와는 달리 Old 영역의 GC는 Heap 사용량이 특정 threshold 값을 넘어가면 실행된다.

유효한 객체들은 young, old GC동안 live 영역으로 이동하는데, 이때 virtual memory address로 이동하기 때문에 객체들 사이에 비어있는 공간이 생기는 memory 파편화가 발생하지 않는다.

전체 heap에 대해서 GC가 일어나지 않고 일부 region에서만 하기 때문에 큰 heap을 가질 경우 유리하다.

사용자가 GC pause time을 어떻게 목표로 하느냐에 따라 G1은 young 영역과 전체 heap size를 자동으로 조절한다.

Java Heap이 많이 필요한 경우 G1을 사용하는 것이 유리하다.

Parallel GC와 CMS GC의 절충안 정도의 GC 알고리즘을 사용하기 때문에 준수한 throughput, latency를 제공한다.

 

'자바 > 가비지컬렉션' 카테고리의 다른 글

자바 메모리 구조 및 가비지 컬렉션  (0) 2019.05.07
CMS gc 과정  (0) 2019.05.07
댓글
댓글쓰기 폼