https://www.youtube.com/watch?v=FMUpVA0Vvjw 

Why Review ?

Java의 Garbage Collector 메모리 관리 소프트웨어에 대해 알아봄으로써, JVM 기반의 소프트웨어에 대해 더 자세히 알고싶다.

 

목차

1. GC가 왜 필요한가?

    1-1. GC의 장점

    1-2. GC의 단점

2. GC 알고리즘

3. JVM의 GC

4. JVM GC 튜닝 맛보기

 

1. GC가 왜 필요하지? 

- 프로그램이 동적으로 할당햇던 메모리 영역(Heap 영역) 중 필요없게 된 영역(어떤 변수도 가리키지 않음)을 알아서 해제한다.

- 자바와 자바스크립트에서는 GC가 동작한다.

- 제대로 해제하지 않으면 Memory Leak 이 발생하기도 함.

1-1. GC의 장점

- 메모리 누수가 사라진다.

- 해제된 메모리에 접근을 막고, 해제한 메모리를 또 해제하는것을 막는다.

1-2. GC의 단점

- GC 작업은 순수 오버헤드

- 개발자는 언제 GC가 메모리를 해제하는 모른다.

- 실시간성이 매우 중요할경우 GC에게 맡기는것은 불안정할 수 있다.

 

2. GC 알고리즘 - Reference Counting 과 Mark And Sweep

GC는 어떻게 해제할 동적 메모리 영역들을 알아서 판단하는 2가지 알고리즘에 대해 알아보자

 

먼저  Root Space :  스택변수, 전역변수 등 Heap 영역 참조를 담은 변수를 의미한다. 를 알아둔다.

 

Refrence Counting은 Heap 영역에 선언된 객체들이 각각 reference Count라는 숫자들이 담겨있는것을 의미한다.

- 해당 객체에 접근할 수 있는 방법이 하나도 없다면, 즉, refererence Count가 0 이라면 가비지 컬렉션의 대상이 된다.

- 하지만, Reference Couting 알고리즘의 문제점은 순환참조 문제가 있습니다. Root Space 속에서의 HeapSpace의 연결을 모두 끊는다면, 서로가 서로를 참조하는 경우에는 reference Count가 계속 1로 유지되기에 memory leak 이 발생됩니다.

 

Mark And Sweep 알고리즘은 순환 문제를 해결합니다.

- 이 알고리즘은 Root 에서부터 해당 객체에 접근가능한지를 해체의 기준으로 삼는다.

- 연결된 객체들은 마킹한다. Mark, Reachable

- 연결이 끊겨진 객체들은 지운다. Sweep., UnReachable

-  Sweep 이후에 분산된 메모리가 정리되며, Memory Compaction이 일어나며 Memory 파편화가 사라진다. 하지만 필수는아님.

- Mark And Sweep을 통해 순환참조되는 객체도 모두 지울 수 있다.

- 자바와 자바스크립트가 Mark And Sweep 방식을 사용한다.

- 단점은 객체의 reference Count가 0 이되면 지워지는것과 달리 Mark and Sweep은 의도적으로 GC를 실행시켜야한다.

즉, 애플리케이션 실행과 GC 실행이 병행된다

- Application의 사용성을 유지하면서 효율적으로 GC를 실행시키는것이 어려운 방식. 

 

JVM의 GC - JVM 구조, 크게 3가지 영역으로 구성된다.

1. Class Loader

- JVM은 byte code를 읽고 class 정보를 Heap/Method 영역에 저장한다.

2. JVM Memory

- 실행중인 프로그램을 Memory에 올려둔다.

3. Executuon Engine

-바이트 코드를 네이티브 코드로 변환시켜주고 GC를 실행시켜주는 실행엔진.

 

- JVM은 OS로부터 Memory를 할당받은 후 해당 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

- 모든 쓰레드가 공유하는 영역으로 Method Area와 Heap 영역이 있고, 

- 각 쓰레드마다 모두 각각 생성하는 JVM Language Stack, PC register, Native Method Stacks가 있다. 이것들은 각각의 스레드가 사라지면 사라진다.

 

Method Area

- 프로그램의 클래스 구조를 메타데이터처럼 가지며, 메서드의 코드들을 저장해둔다.

- Heap은 어플리케이션 실행중에 생성되는 객체 인스턴스를 저장하는 영역이다.

- 이 Heap 영역이 Garbage Collector에 의해 정리되는 부분.

- Stack은 메서드 호출을 스택프레임이라는 블록으로 쌓아, 로컬변수, 중간연산 결과들이 저장되는 영역이다.

- pc register은 스택 프레임의 저장 주소를 가지고 있다.

- Native Method Stack는 c/c++의 Low level의 코드를 실행한다.

 

JVM의 Root Space가 어디있는지 아는것이 중요하다.

아래 3가지에 존재한다.

- Stack의 로컬변수

- Method Area의 Static 변수

- Native Method Stack의 JNI 참조

 

이제 JVM GC의 RootSpace가 어디있는지 알고있으니, Mark And Sweep 방식에 대해 알 수 잇다.

 

1. 의도적으로 GC를 실행시켜야한다. 

- JVM GC에게는 실행시켜야하는 타이밍은 'JVM의 Heap'영역'에 의해 결정된다.

- Heap영역에는 Young Generation과 Old Generation 이 나누어져잇다.

  - Young Generation은 또 다시 세 영역 으로나뉜다. Eden, Survivial0, Survival1 로 나뉜다. 

    - Eden : 새롭게 생성된 객체 들이 할당된다.

    - survival 0, 1 : Minor GC로부터 살아남은 객체들이 존재하는것. 특별규칙은 survival 0, survival1 둘중하나는  반드시           비어있어야한다.

    - 계속해서 새로운 객체가 추가되면 Eden이 꽉차는 시점이 발생되는데 이때, Minor GC가 발생.

    - 이때 Root Space에서 Reachable이라 판단된 객체는 필요한 객체이므로 Survival 0 으로 옮겨져서 살아남는다.                    Survival 뜻이 살아남은 객체라는 뜻이다. 그리고 객체의 bit가 1로 증가한다. Age-bit라고도 한다.  그리고 이동할때마다 Age-bit는 1 씩 증가한다.

     - 또다시 Eden 공간이 꽉차서 Reachable한 것들과 Survival0 에 있는 것들은 모두 Survival 1 로 이동한다.

      - 이때 한번 더 이동되면서 Age-bit가 3이 되면,(혹은 일정수준)으로 이동되면 오래 살아남을 객체구나 라고 판단하여            Old generation으로 이동된다. Java 8 기준으로는 age-bit가 15가 되면 Promotion이 진행되고 Old generation으로 이         동한다.

      - 언젠가 Old Generation이 꽉 차면, Major GC가 작동하며 Mark and sweep이 발생하게 된다.

- 왜 굳이 Old, Young으로 나누었을까? 분석해보니 대부분의 객체 수명이 매우 짧다. GC도 결국비용이기에 메모리의 특정부분만 탐색하여 해제하는것이다.

- Old Generation은 1가지 영역이다.

 

2. 어플리케이션 실행과 GC 실행이 병행된다.

- "Stop the World"라는 개념란?  "GC를 실행하기 위해 JVM이 어플리케이션 실행을 멈추는것이다."

-  이 Stop the World를 최소화시키는것이 어려운 최적화 작업이다.

- Serial GC

  - 하나의 쓰레드로 GC를 실행한다.

  - Stop the World 시간이 길다.

  - 싱글 쓰레드 환경 및 Heap이 매우 작을떄 사용한다.

- Parallel GC는 여러개의 쓰레드로 가비지 컬렉션을 실행한다.

  - 그에 따라 Serial GC보다 빠르다.

  - JAVA 8 의 default 실행방식

  - Stop the World 시간을 최소화하기 위해 고안되다.

  - GC 작업을 어플리케이션과 동시에 실행, 하지만 메모리와 CPU를 많이 사용하고, 무엇보다 Mark And Sweep 과정 후 메모리 파편화를 해결하는 Compaction 이 기본적으로 제공되지 않는다.

- G1 GC가 등장. 

  - Garbage First (G1)

  - Heap을 일정 크기의 Region으로 나누어 사용

  - 어떤 영역은 Young, 어떤 영역은 Old generation으로 나눈다.

  - 런타임에 G1 GC가 필요함에 따라 영역별 Region 개수를 튜닝한다.

  - Java 9 부터 default GC 방식

 

JVM GC 튜닝 맛보기

- GC 튜닝은 성능의 마지막 단계이다.

- 객체 생성을 줄이려는 코드레벨의 노력이 먼저 필요 ( 예시로 String 대신 StringBuilder를 사용 )

- Old Generation으로 넘어가는 개체 최소화하기

- Major GC 시간을 짧게 유지하기

- 한정된 Heap 영역에 Young, Old Generation을 얼마만큼 할당해야하는지.

- 메모리가 너무 크다면 GC는 적게 일어나겠지만, 메모리가 작다면 GC는 자주 일어나지만 금방 끝난다.

- 어플리케이션의 특성 고려필요.

 

JVM 튜닝

- initalHeapSize = 65G, MAxHeapSize=10기가

- GC 방식은 Paraell GC.

- JVM 메모리 모니터링. JDK 설치 시 기본제공되는 jstat이라는 툴을 활용

- $ jstat -gcutil -t 8844 (port) 1000 10 (1초에 한번씩 총 10번 모니터링)

- S0(survival 0), S1 (suvirival 1), E (Eden 영역) O (Old Generation) YGC(Young Generation GC 이벤트 수 ), YGCT(Young Generation 총 GC 시간), FGC ( 전체 이벤트 GC 수) FGCT ( 전체이벤트 GC 시간), GCT ( 전체 GC 시간)

 

1. 정보를 출력하는 10초 사이에 애플리케이션 글 작성요청을 보낸다.

2. 새로운 객체 할당되며 Eden 영역의 사용률이 늘어난다.

3. Young GC 영역에서 총 19번의 GC가 0.314 초 동안 실행되다. => 0.314 / 19 = 0.016 -> Minor GC가  0.016 초

 Major GC는 총 3번 실행되고, 0.291 초 동안 실행되었다. Major GC가 0.291 /3 = 0.097초 

4. DB Connection이 1초이상 타임아웃 상황이라면 GC가 장애원인이 될 수 있음.

5. jstat gccapacity 명령을 통해 프로세스가 heap 영역을 얼마나 사용중인지 정확한 수치를 알 수 있다.

$ jstat -gccapacity -t 8844 1000 10

-. NG로 시작되는 지표들은 new, Young Generation. OG로 시작하는 지표들은 Old generation 영역을 의미

- CMN은 영역의 최소 할당크기. CMX 는 영역의 최대 허용크기를, C는 영역의 현재크기를 각각 KB로 나타낸다.

- 현재 NGC는 약 323 MB,. OGC는 686 MB 사용중.

- 이와 같이 분석 후 JVM Option을 다르게 설정한다. Heap 영역 크기 조절, Young Generation 영역의 크기, 앞서 살펴본 GC 실행방식들을 변경할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts