Java 메모리 구조 및 GC 알고리즘 정리

JAVA/고급 자바|2019. 9. 23. 22:07

자바 메모리 구조는 1.8 이후로 일부분 바뀌었다.

이 부분에 대한 정리를 다시 하고 싶었고 GC 알고리즘에 대한 종류와 상세 내용을 정리하고 싶었다. 그럼 이 두 가지 사항에 대해 가볍게 정리해보자.

Java 메모리 구조

Method Area

프로그램이 실행되는 도중에 아직 사용되지 않은 클래스들의 코드는 new를 통해 클래스의 인스턴스가 생성되면 JVM Method Area에 인스턴스 변수, 메스드 코드, 클래스 변수등을 저장한다. 해당영역은 모든 쓰레드 사이에서 공유되고 static 키워드로 생성된 변수 또한 저장을 Runtime Constant Pool 영역에 저장한다. 실 데이터를 저장하는 것이 아니라 레퍼런스만 저장하며 실제 데이터는 Heap 영역에 저장한다.

 

JVM Language stack

각 스레드들은 생성과 동시에 각자의 stack을 생성하게 되는데 이 영역에 메소드가 실행될 때 사용된 메스드의 데이터들을 저장한다. 그리고 메서드 실행이 끝나면 해당 stack영역은 사라진다.

 

PC Registers

각 스레드별로 PC Registers가 존재하며 JVM 머신이 가장 최근에 실행한 명령어의 주소를 저장한다.

 

Native Method Area

Native Library에 의존하는 native 코드들을 저장하는 곳이다. (JNI)

 

Permenent (Java8 metaspace 대체)

permenent 영역에 로드된 클래스의 메타 정보와 static한 변수들 정보들이 담겨져 있는데 그 중 static 영역과 상수 영역은 heap으로 옮겨졌다.

-XX:MetaspaceSize : JVM이 사용하는 네이티브 메모리 
-XX:MaxMetaspaceSize : metaspace의 최대 메모리 

 

Heap 영역

GC가 발생되는 대표적인 영역이며 new를 통해 인스턴스가 동적으로 생성된 데이터와 배열정보를 저장하는 공간으로 xms, xmx등의 옵션으로 기본 힙사이즈를 설정할 수 있다. 해당 힙사이즈도 모든 쓰레드 사이에서 공유된다.

-Xms : JVM 시작 시 힙 영역 크기
-Xmx : 최대 힙 영역 크기

힙 영역은 GC가 발생되는 방법에 따라 Young, Old영역으로 나뉘게 된다.

1. Young 영역 

Eden

새롭게 할당된 데이터가 쌓이는 곳으로 일정주기 동안 참조가 유지되면 Survivor로 옮겨진다. Survivor로 옮겨지지 못한 데이터는 GC에 의해 청소된다.

Survivor

Eden영역에서 넘어온 데이터가 1 또는 2영역으로 나눠서 저장된다. 참조가 살아있는 경우 주기에 맞춰서 다른 Survivor영역으로 이동하고 그렇지 못한 데이터들은 GC에 의해서 처리된다. 위와 같은 경우를 Minor GC라고 한다. 

-XX: NewRatio      : New영역과 Old 영역의 비율
-XX: NewSize       : New 영역의 크기
-XX: SurvivorRatio : Eden 영역과 Survivor 영역의 비율

 

2. Old 영역

Survivor 영역에서 오래 살아남은 데이터의 경우 Old 영역으로 넘어가게 된다. Old 영역은 이렇게 넘어온 데이터가 많기 때문에 Young크기 보다 더 크게 설계되며 이부분에서 발생된 GC를 Major GC라고 한다.

 

GC 알고리즘

YOUNG, OLD GC가 발생되는 알고리즘 종류에 대해 정리해보자. 우선 GC의 경우 mark > sweep > compaction 작업이 순서대로 동작한다. 우선 GC 대상을 고르는 mark 작업이 선행되고 실제 제거를 수행하는 sweep가 동작한다. 그리고 메모리의 파편화가 된 부분을 채워 나가는 Compaction 작업으로 마무리한다.

Serial GC

위에서 언급한 3가지 작업이 진행되는 간단한 GC 알고리즘이다. 이 GC의 경우 처리하는 쓰레드가 단 하나이기 때문에 처리하는 과정 동안에 발생하는 STW (Stop the world) pause 시간이 길다.

 

Parallel GC

Serial GC에서 동작하는 스레드가 하나였기 때문에 문제가 자주 발생하였는데, 여기에 작업을 진행하는 스레드를 추가하여 병렬로 작업을 진행한 알고리즘 이다.

왼쪽부터 Serial GC, Parallel SerialGC (https://www.oracle.com/technetwork/java/index.html)

CMS GC

기존에 사용되던 GC 알고리즘 보다 STW pause 시간을 줄이기 위해 고안된 방법으로 없애야 하는 데이터를 정확하게 선별하는 작업이 추가로 진행된다. 그만큼 연산작업이 추가되어 CPU같은 리소스 자원 사용이 증가하였다. 최초 GC 판단하는 initial Mark, initial mark때 선정된 객체를 참조 하는 객체의 GC 대상인지 판단하는  Concurrent Mark, 마지막 검증 작업을 하는 Remark작업을 통해 대상을 선정한 후 Concurrent Sweep 작업을 통해 데이터를 지운다. 그리고 기존 Serial GC와의 차이점은 데이터를 sweep한 후 compaction 작업을 자주 진행하지 않는다. 파편화된 메모리를 자주 매꾸면 그만큼 오버헤드가 많이 발생할 수 있기 때문에 심각한 파편화가 발생했을 때만 매꾼다.

 

G1GC

기존에 GC 알고리즘과는 다른 알고리즘이 나온게 G1GC이다. 기존에는 Eden, Survivor, Old, Permanent영역으로 정확하게 나누어져 있었다. 하지만 G1GC 사용할 경우 heap 영역을 2048개 region 영역으로 쪼개고 이 지역의 크기는 G1HeapRegionSize를 통해 32mb 까지 지정이 가능하다.

또한 새로운 형태의 상태값이 생겼는데 Humongous와 Available/Unused이다. Humongous는 region크기의 50%를 초과하는 큰 데이터를 저장하기 위한 곳이고 Available/Unused는 아직 사용하지 않는 region을 뜻 한다.

각 Region은 Eden, Survivor, Old, Permanent, Humongous, Available/Unused 상태로 지정이 가능하고 데이터가 가장 많이 찬 Region에서 GC가 발생된다. 

G1GC 사용 시 heap 영역 (https://c-guntur.github.io/java-gc/#/6)

 

 

출처

https://www.guru99.com/java-virtual-machine-jvm.html
https://d2.naver.com/helloworld/1329

댓글()

엘라스틱 서치 (elasticsearch) fielddata

엘라스틱 서치에서 aggregations를 사용하여 text 필드를 그룹화 하려고 했다.

하지만 이런 오류와 함께 사용이 되질 않았다.

1
2
Fielddata is disabled on text fields by default. 
Set fielddata=true on [your_field_name] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory.
cs


그래서 엘라스틱 서치 문서를 살펴보던 중 text 필드에 fielddata에 대해 알게 되었다.

대 부분의 필드 들은 기본적으로 자신의 필드가 검색가능하도록 인덱스 처리가 된다. 그러기 위해서 대부분의  필드 들은 데이터 패턴을 사용하기 위해서 디스크에 doc_values를 index-time으로 사용할 수 있다하지만 text field doc_values 지원하지 않는다대신에 text 필드는 fielddata라고 불리는 in-memory 구조의 query-time 사용한다 데이터 구조는 필드가 집계정렬 또는 스크립트에 처음 사용될  필요에 따라 작성된다 fielddata 디스크의 각각의 세그먼트로 부터  색인을 읽어 결과를 JVM heap 메모리에 저장한다.

하지만 이 비용이 생각보다 크기 때문에 기본적으로 사용이 false로 되어 있다. 그렇지만 집계 기능을 사용하기 위해서는 해당 기능을 사용해야한다.

text field의 fielddata를 사용하는 방법


1. keyword 사용

기존에 사용하는 text 필드는 원래 기능 그대로 full text searches를 위해 사용하고, aggregations 기능을 사용하기 위해서 doc_values기능을 사용할 수 있는 keyword 필드를 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
curl -X PUT "localhost:9200/my_index" -'Content-Type: application/json' -d'
{
  "mappings": {
    "_doc": {
      "properties": {
        "my_field": { 
          "type": "text",
          "fields": {
            "keyword": { 
              "type": "keyword"
            }
          }
        }
      }
    }
  }
}
cs


2. text field 기능 사용 시 fielddata 옵션 추가

1
2
3
4
5
6
7
8
9
PUT my_index/_mapping/_doc
{
  "properties": {
    "my_field": { 
      "type":     "text",
      "fielddata": true
    }
  }
}
cs


자세한 내용은 해당 링크 참조.
https://www.elastic.co/guide/en/elasticsearch/reference/current/fielddata.html

댓글()

규칙 55 - 신중하게 최적화하라

JAVA/Effective Java|2018. 5. 29. 23:49

모든 프로그래머가 알아둬야 하는 최적화에 관련된 격언이 있다.

1. 맹목적인 어리석음을 비롯한 다른 어떤 이유보다도, 효율성이라는 이름으로 저질러지는 죄악이 더 많다.
2. 97%는 효율성을 잊어버려라.  섣부른 최적화는 모든 악의 근원이다.

그리고 프로그램을 작성하면서 기준을 삼아야 할 내용에 대해 소개한다.

[기준]
빠른 프로그램을 만들려고 처음부터 노력하지말고, 좋은 프로그램을 만들려 노력하라.
-> 좋은 구조를 가진 프로그램은 빠른게 변경하는데 어렵지 않다.
-> 정보은닉의 원칙을 지키는 것이 좋은 구조를 갖는것에 첫 번째 항목이다.

설계를 할 떄는 성능을 제약할 가능성이 있는 결정들을 피하라.
-> 특히 통신 API, 프로토콜 정의서는 변경하기 어렵기 때문에, 신중하게 코딩해야한다.

API를 설계할 떄 내리는 결정들이 성능에 어떤 영향을 끼칠지를 생각하라.
-> public 자료형을 무분별하게 사용하면 잘못된 객체 생성등으로 인해 성능에 이슈가 생길 수 있다.


코드를 최적화한 이후에 코드가 성능이 좋아졌는지 확인을 해야하는데, 전통적인 정적 컴파일 언어들에 비해, 프로그래머가 작성한 코드와 CPU가 실행하는 코드 사이의 "의미론적 차이"가 훨씬 크기 때문에 최적화 결과로 성능이 얼마나 좋아질지 안정적으로 예측하기 어렵다.
-> 특히 자바의 경우 JVM 구현마다, 릴리즈마다, 프로세스 마다 다르다. 
-> JVM 구현이나 하드웨어 플랫폼마다 다른 성능을 내기 때문에 타협적 결정을 내려야 할 때가 있다.




결론을 내면, 자잘한 성능을 고치려고 노력하지말고 구조적 알고리즘 문제를 먼저 생각하라. 잘못된 알고리즘 선택으로 인해 잘못 설계된 프로그램은 자잘한 성능개선으로 문제를 잡을 수 없기 때문이다.


출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙55 인용.

댓글()

java 메모리 누수 주된 원인

JAVA/JAVA 관련|2018. 5. 28. 22:31

자바 메모리 누수 측정 방법

  • 자바 프로그램의 실제 메로리 사용량은 시스템의 작업관리자에서 나오는 메모리 사용량으로는 측정  없기에 디버그 출력으로 totalMemory() - freeMemory() 출력하거나개발 도구를 사용하여 측정하는 것이좋다.

 

GC 알고리즘

  • 메모리가 GC 부터 해소가 되지 않는 루트 참조 객체(직간접적으로 참조가 되는 모드 객체) 크게 3가지경우이다.
  1. Static 변수에 의한 객체 참조
  2. 모든 현재 자바 스레드 스택내의 지역 변수매개 변수에 의한 객체 참조
  3. JNI 프로그램에 의해 동적으로 만들어지고 제거되는 JNI global 객체 참조

이러한 경우에 사용할  있는 객체로 분류되어 GC에서 가져가지 않아 메모리가 누수될  있다.


자바 메모리영역은 3가지로 구성되어 있다.

Heap : 사용자가 생성하는 object

Metaspace : classload, 메소드변수 정보를 저장하는 영역

Native : OS 자원을 보관하는 영역

댓글()