본문 바로가기

Java

JAVA GC가 발생하는 시기(Garbage Collector)

728x90
반응형

JAVA GC?

java에서 메모리는 GC가 관리하기 때문에 개발자가 따로 메모리를 관리하는 로직을 넣을 필요가 없다.

(만들어서는 안된다)

Garbage Collector 쓰레기를 정리하는 작업으로 자바에서 쓰레기는 객체를 의미한다.

객체가 메모리를 점유하고, 필요가 없으면 메모리에서 해제되어야 한다. 


JAVA Runtime Data Area

 

프로그램이 실행되면 JVM은 OS로 부터 필요한 메모리를 할당받은 후 용도에 따라 여러 영역으로 나눈다.

자바 컴파일러(javac)가 소스코드(java)를 읽어서 바이트 코드로 변환한다.(.class)

그리고 클래스로더를 통해 JVM으로 로딩하는데 로딩된 class파일들은 실행엔진을 통해 해석된다.

해석된 바이트코드는 Runtime Data Area에 배치되어 수행된다. 

 

RuntimeDataArea는 JVM이 OS로부터 할당받은 메모리 공간을 말하는데 크게 5가지로 나눌 수 있다.

 

1. PC Register

JVM에서 한 번에 많은 스레드를 실행할 수 있는데 스레드에는 자체 PC(Program counter) 레지스터가 있다.(스레드별로 존재함)

어느 시점이든 JVM스레드는 단일 메소드의 코드, 즉 해당 스레드에 대한 현재 메소드를 실행하는데 해당 메소드가 기본이 아니면 PC레지스터에서 실행중인 JVM명령의 주소가 포함된다. 네이티브 메소드일 경우 PC레지스터에 정의되지 않는다.

네이티브 메소드는 JVM을 거치지 않고 API를 통해 바로 수행한다. 

 

2. JVM Stack

각 JVM스레드에는 스레드와 동시에 생성되는 JVM스택이 있다. (각 스레드별로 생성되기 때문에 다른 스레드에서는 접근할 수 없다)

로컬변수와 부분결과를 보유하고 메서드의 호출이 종료될때 stack 에서 제거된다. 메소드의 지역변수, 매개변수, 임시변수 그리고 메소드를 호출한 주소등을 저장한다. 

 

3. Heap

JVM에서 모든 스레드간에 공유가되는 힙으로 클래스의 인스턴스 및 배열에 대한 메모리가 할당되는 영역이다.

가상머신 시작시 생성되고 객체에 대한 힙스토리지는 GC에 의해 회수된다.

명시적으로 할당을 해제하지 않는다.

클래스를 이용해서 instance를 생성하면 heap에 저장된다. 

 

4. 메서드 영역 

JVM에서 스레드간에 공유되는 메모리 영역.

클래스, 인터페이스, 필드, 메소드, static변수 등 바이트 코드를 보관한다.

 

5. Run-Time Constant Pool

클래스파일에는 constant_pool이라는 정보가 포함되어 있는데 이 constant_pool에 대한 정보를 실행시에 참조하기 위한 영역이다. 

실제 상수값도 포함될 수 있지만 실행시에 변하게 되는 필드 참조 정보도 포함한다.

 

6. Native Method Stack

Java코드가 아닌 다른 언어로된 코드들을 실행할 때 스택 정보를 관리한다.

 

 

여기서 heap영역과 method영역은 JVM이 시작될 때 생성된다. 


GC의 원리

gc가 하는 역할은 메모리 할당, 사용주인 메모리 인식, 사용하지 않는 메모리 인식 세 가지다. 

사용가능한 메모리 영역이 없는데 메모리를 할당하려고 하면 OutOfMemoryError가 발생해서 JVM이 다운될 수도 있다. 

Young영역 Old영역
Eden Survivor1 Survivor2 메모리영역 

먼저 메모리에 객체가 생성되면 Eden영역에 객체가 지정된다.

Eden영역에 데이터가 꽉차면, Survivor영역으로 이동한다. survivor1과 2사이에 우선순위가 있는건 아니고 둘 중의 한 영역은 반드시 

비어있어야 한다. 비어있는 영역에는 Eden영역 GC후 살아있는 객체들이 이동한다.

한 survivor영역이 꽉 차면 GC되면서 Eden영역에 있는 객체와 꽉찬 survivor영역에 있는 객체가 비어있는 survivor영역으로 이동한다.

 

이런 작업을 반복하면서 survivor1과 2를 왔다갔다하다가 남은 객체들은 Old영역으로 이동한다.

객체의 크기가 아주 클 경우 survivor영역을 거치지 않고 바로 Old영역으로 이동할 수 있다. 

예를 들어 survivor의 크기가 16MB이면 20MB를 점유중인 객체가 Eden에서 survivor로 갈 수 없다.

그럴 경우 Old영역으로 이동한다. 

 

GC의 종류

마이너GC: Young영역에서 발생하는 gc

메이저GC: Old영역이나 Perm영역에서 발생하는 gc

 

gc가 발생하거나 객체가 다른 영역으로 이동할 때 병목이 발생하면서 성능에 영향을 준다. 

핫스팟JVM에서는 스레드 로컬할당버퍼를 사용해서 스레드별 메모리버퍼를 사용하고 있는데 다른 스레드에 영향을 주지 않는

메모리 할당 작업이 가능하다.

 

다섯가지 GC방식을 지원한다.

  • Serial Collector 
    • Young영역과 Ole영역이 시리얼하게 처리되며 하나의 CPU를 사용한다. 
      이 처리를 할 때 stop-the-world라고 표현한다. 
    • Eden영역이 꽉 차면 survivor2(비어있는)영역으로 이동하는데 너무 큰 객체는 바로 Old영역으로 이동한다.
    • 이미 꽉 차있는 survivor1영역에서 살아있는 객체는 다른 survivor2영역으로 ㅇ동한다. 
    • 만약 survivor2영역이 꽉 찼으면 Eden영역이나 survovir1에 남아있던 객체들은 Old로 이동한다. 
  • Parallel Collector
    • 이 방식의 목표는 다른 CPU가 대기 상태로 남아있는 것을 최소화한다. 
    • CPU를 사용하기 때문에 부하를 줄이고 처리량을 증가시킨다. 
  • Parallel Compacting
    • Parallel Collector와 다른 점은 Old영역 GC에 새로운 알고리즘을 사용한다. 
    • Old영역에서는 살아있는 객체를 식별하는 표시단계, 이전GC를 수행하여 컴팩션된 영역에 살아있는 객체를 조사하는
      종합단계, 컴팩션을 수행하는 컴팩션 단계를 거친다.
    • 수행 이후에는 컴팩션된 영역과 비어있는 영역으로 나뉜다.
  • Concurrent Mark-Sweep
    • 힙메모리 영역의 크기가 클 때 적합한 방식으로 Young영역에 GC는 Parallel Compacting과 동일하다.
    • 객체를 찾는단계-> 서버수행과 동시에 살아있는 객체 표시단계 -> 표시단계동안 변경된 객체에 다시 표시하는 단계
      -> 표시되어 있는 쓰레기 정리 단계 
    • CMS콜렉터 방식은 2개이상 프로세서를 사용하는 서버에 적당하다.
    • JAVA9부터 deprecated되었고 14부터는 중지되었다.
  • Garbage First Collecton
    • 위의 GC는 모두 young영역과 old영역으로 구성되어있는데 G1은 바둑판 모양처럼 생겼다. 

    • yong영역과 old영역이 물리적으로 나뉘어져있지 않고 모두 동일하다. 
    • 몇 개의 구역을 young으로 지정하고 young영역에 할당된 데이터가 꽉 차면 gc를 실행한다.
    • gc에서 살아있는 객체를 survivor영역으로 옮기고 old영역으로 이동된다. 
    • 초기 표시 단계(Old영역에 있는 객체에서 Survivor영역의 객체를 참조하고 있는 객체에 표시) ->
      기본 구역 스캔(YoungGC수행 전 survivor영역 스캔) -> 컨커런트 표시(힙에 살아있는 객체) ->
      재표시 단계(힙에 살아있는 객체 표시) -> 청소(살아있는 객체, 비어있는 구역 식별 후 필요없는 개체 삭제)
      -> 복사(살아있는 객체들을 비어있는 구역으로 모은다) 

 

GC는 결국 각 영역에 할당된 크기의 메모리가 허용치를 넘을 때 수행하는 것으로 개발자가 컨트롤 할 수 없다.

 

강제로 GC 시킬 경우  ..> System.gc();
GC를 수행하는 동안 GC를 실행하는 스레드를 제외하고는 모든 스레드가 멈춘다.
그러므로 성능에 영향을 미칠 수 있다. 
728x90
반응형