9. Java의 GC에 대해 설명해 주세요. #47
Replies: 5 comments
-
G1 GC
GC의 종류
...
왜 이름이 G1일까?G1 GC의 G1은 (Garbage-First) 의 약자로, 가장 많은 가비지가 있는 영역부터 수집하는 핵심 전략을 갖고 있다. 이렇게 하면 가장 효율적으로 메모리를 재활용할 수 있으며, 주어진 일시 중지 시간(pause time) 목표 내에서 가능한 한 최대의 메모리를 회수하는 것이 가능해진다. G1은 어떤 안정성 문제가 있었을까?G1은 Java 7에서 처음으로 사용 가능하게 되었는데, 당시에 더 오래 된 Parallel GC나 CMS GC 초기 버전의 G1 GC는 일부 케이스에서 예상치 못한 행동이나 성능 문제를 일으킬 수 있었다.
이러한 문제점들은 시간이 지나면서 Java 업데이트와 함께 개선되었고, G1은 어떻게 기본 GC가 될 수 있었을까?G1이 기본 GC로 채택된 것에는 여러가지 중요 요인들이 있다.
Ref. # |
Beta Was this translation helpful? Give feedback.
-
들어가며
말 그래도 쓰레기를 수집하는 기능이며 쓰레기는 실제 쓰레기를 뜻하는게 아니라 개발자가 동적으로 할당한 메모리 영역 중 더 이상 쓰이지 않는 영역을 말합니다. 가비지 컬렉션은 그러한 영역을 자동으로 찾아내어 해제하는 기능을 합니다. 그러면 가비지 컬렉션은 어떻게 더 이상 쓰이지 않는 영역을 탐지하는 알고리즘이 어떤 것이 있는지 확인해 보겠습니다. 알고리즘
참조 횟수 카운팅 GC ( Reference Counting Garbage Collection )
예를 들어 A 메모리가 B 메모리를 참조하면 B 메모리의 참조 횟수를 1을 더하고, A 메모리가 B 메모리 참조를 중단하면 B 메모리 참조 횟수를 1을 뺍니다. 만약 1을 뺐을 때, 참조 횟수가 0이 되면 해당 메모리에 아무도 접근을 못 하는 것이므로 해당 메모리를 해제합니다. 장점다른 또한 참조횟수가 0이 되자마자 소멸한다는 장점도 있습니다. 단점크게 두 가지 단점이 존재합니다.
추적 기반 GC ( Tracing Garbage Collection )가장 많이 사용되는 이 방식을 사용하면 메모리 조사의 시작점이 있어야 할 텐데, 항상 접근 가능한 메모리를 추적 기반 GC 알고리즘은 총 5가지의 알고리즘을 조사하였으며 목록은 다음과 같습니다.
이 중에서 먼저 위에 위에 있는 2가지의 알고리즘에 대해 알아 보도록 하겠습니다. Mark-Sweep Algorithm
마킹이 안 된 메모리는 전부 해제한 후 살아남은 객체의 마킹 정보를 초기화합니다.
이 방식대로 수행하면 접근이 가능하거나 불가능한 메모리를 완벽하게 분류해서 해제하는 것이 가능합니다. 하지만 프로그램 실행 도중 메모리가 변경되면 마킹을 다시 해야 하기 때문에 프로그램을 통째로 정지( 이러한 이유 때문에 Mark-Sweep-Compact Algorithm위의 단편화는 메모리의 빈 부분들을 합쳐보면 충분히 많은 메모리가 있음에도 불구하고 새로운 객체를 할당할 수 없는 상황이 생깁니다. 또한 새로운 객체를 할당하기 위해 메모리 상의 빈 공간을 뒤지는 과정 자체가 성능에 악영향을 미치기 때문에 프로그램도 느려질 수 있습니다. 그래서 하지만 이러한 점진적 GC ( Incremental GC )위의 2가지 추적 기반 GC ( Tracing Garbage Collection )을 소개하였는데 해당 알고리즘들은 문제점들이 있었습니다. 위의 방식들처럼 마킹과 해제를 한 번에 하지 않고, 여러 번에 걸쳐서 수행하는 방식입니다. 위에서 설명한 그러면
Tri-color Marking Algorithm기존에는 접근/불가능이라는 2가지의 색으로만 마킹을 했다면 3가지의 색은 아래와 같이 구분합니다.
위 작업들이 끝난 후 만약 회색으로 마킹된 메모리가 존재하지 않으면 모두 흰색이나 검은색이므로 모든 메모리의 접근 가능 여부를 결정합니다. 이런 방식을 이용하면 임의로
위와 같은 문제를 해결하기 위해
Copying Algorithm
위 그림으로 예를 들면, 두 개의 메모리를 A, B라고 했을 때
이후 (1)부터 (4)까지의 과정을 반복합니다. 해당 알고리즘의 장점으로는 새로운 공간에 단편화 없이, 연속된 메모리 공간에 차곡차곡 재배열이 되기에 캐시 효율이 높아집니다. 하지만 단점으로는 처음부터 메모리를 잡아두고 시작하다 보니 메모리 공간을 많이 사용하게 됩니다. Generational Algorithm객체의 라이프 사이클을 자세히 살펴보니, 한 가지 특이한 현상이 있습니다. 이러한 현상을 토대로 아래 두 가지 가정 (Weak Generational Hypothesis)을 전제 삼아 만들어진 방식이 해당 알고리즘입니다.
상대적으로 크기가 작은 이 세대를 나누는 기준은 구현 방식마다 다른데 위의 그림을 보면, 객체는
해당 알고리즘의 장점으로는 대부분의 객체는 정리참조 횟수 카운팅 GC ( Reference Counting Garbage Collection )의 장점으로는 구현이 쉽고추적, 참조 횟수가 0이 되면 즉시 소멸한다는 장점이 있지만 단점으로는 오버헤드가 많고, 무한 참조가 발생합니다. 그래서 추적 기반 GC ( Tracing Garbage Collection )를 사용하여 참조 횟수 카운팅 GC의 단점을 극복하였습니다.
결론현재 위의 내용에는 없지만 Ref
|
Beta Was this translation helpful? Give feedback.
-
1. 들어가며GC 란?GC는 , Java 의 GC책에서도 명시되어 있는 것 처럼, Java에서는 개발자가 메모리를 직접 건드리는 것이 아니라 public String makeQuery(String code){
String queryPre = "select * from table_a where a = '";
String queryPost = "' order by c ";
return queryPre = queryPre + code + queryPost;
} 위 예시에서 makeQuery의 메서드 수행이 완료되면, 그렇다면 우리는 메모리 관리에 신경쓰지 않고 개발을 해도 되는걸까? Stop-The-World우리가 다시 말해, 책에서 여러가지 2. Stop-The-World가 발생하는 이유
(1) 메모리 파편화 (Memory fragmentation)메모리 파편화란, 컴퓨터 시스템에서 메모리 공간을 할당하고 해제하는 과정에서 발생하는 현상이며 크게 외부 파편화와 내부 파편화로 나눌 수 있다. 두 종류의 메모리 파편화 모두 메모리를 효율적으로 사용하는데에 있어 치명적이다.
앞서 힙 영역 내의 공간들을 큰 블록으로 만들기 위해서는, 객체를 새로운 주소로 이동 시키고 다시 주소를 참조하는 과정이 필요하다. 이를 위해서 (2) 객체 일관성
메모리 내의 객체들의 상태가 변경될 수도 있고, 다른 쓰레드가 객체를 참조할 수도 있다. 이런 가능성은 잘못된 결과나 의도치 않은 오류를 발생시킬 수도 있다. 예를 들어, 이런 오류를 방지하기 위해서 3. Compaction흩어져 있는 메모리를 압축하는 과정을 통해, 메모리를 효율적으로 사용할 수 있다. 4. Concurrent Mark & Sweep GC (= CMS Collector)앞서 언급했던 것처럼 어떤 알고리즘을 사용하더라도
초기 단계에서 잠시 멈춘 후에, 다른 쓰레드가 실행 중인 상태에서 실행되기 때문에 하지만, 다른 방식보다 메모리와 CPU를 더 많이 사용한다는 점이 단점이다. 또한 참고자료https://golf-dev.tistory.com/68 |
Beta Was this translation helpful? Give feedback.
-
서론저자의 말을 빌려보면 GC가 필요한 상황은 JVM의 메모리 크기도 지정하지 않았고, Timeout이 지속적으로 발생하고 있는 상황이다. 우리는 이 상황을 조금 더 자세히 들여다 볼 것이다. 본론사실, 지금부터 말할 내용은 우리가 지난 4주간 피땀흘려 공부한 내용을 종합하는 것이다. 1. memory leak 식별 및 해결 memory leak이 발생하는 상황은 잦은 GC를 유발하는 대표적인 이유이다. 이슈 16([#16])_메모리 릭(Memory leak)과 GC에서 이미 다루었듯 더 이상 불필요한 메모리가 GC에 의해 해제되지 않으면서 메모리 할당을 잘못 관리된다면 이는 다시 GC를 유발하는 악순환에 빠지게 된다. 2. 불필요한 객체 생성 줄이기
List<Integer> list; // 루프 밖에서 선언
for (int i = 0; i < 100_000_000; i++) {
list = new ArrayList<>();
/* list 사용 */
list = null; // 사용 후에 참조 해제
}
3. 캐시 활용 자주 사용하는 데이터를 캐시하여 반복적인 계산이나 sql 쿼리를 피할 수 있다. 캐시가 사용되는 가장 기본적인 이유 중 하나가 메모리 릭을 막는 방법과 아주 깊은 연관이 있다. 캐시를 사용하여 반복적으로 계산하거나 데이터를 다시 불러오는 대신 이전 결과를 저장하고 다시 사용함으로써 불필요한 메모리 소비를 방지할 수 있다. 예시를 멀리서 찾지 말자. 피보나치 수열을 dp로 풀던 경험을 떠올리면 익숙할 것이다.
import java.lang.ref.WeakReference;
public class WeakReferenceExample {
public static void main(String[] args) {
// 객체 생성
String data = new String("This is a weak reference example");
// 약한 참조 생성
WeakReference<String> weakRef = new WeakReference<>(data);
// data 참조를 해제
data = null;
// 가비지 컬렉터 실행하면 data 객체가 GC에 의해 수거 당함
System.gc();
}
} import java.lang.ref.SoftReference;
public class SoftReferenceExample {
public static void main(String[] args) {
// 객체 생성
String data = new String("This is a soft reference example");
// 소프트 참조 생성
SoftReference<String> softRef = new SoftReference<>(data);
// data 참조를 해제
data = null;
// 가비지 컬렉터 실행 (메모리가 충분한 경우에는 수거되지 않을 수 있음)
System.gc();
}
} import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
public class SoftReferenceCache<K, V> {
private final Map<K, SoftReference<V>> cache = new HashMap<>();
public V get(K key) {
SoftReference<V> softRef = cache.get(key);
if (softRef != null) {
V value = softRef.get();
if (value != null) {
return value; // 캐시에서 유효한 객체 반환
}
}
return null; // 캐시에 해당 객체가 없거나 더 이상 유효하지 않을 때
}
public void put(K key, V value) {
SoftReference<V> softRef = new SoftReference<>(value);
cache.put(key, softRef);
}
public static void main(String[] args) {
SoftReferenceCache<String, String> cache = new SoftReferenceCache<>();
cache.put("key1", "value1");
cache.put("key2", "value2");
// 캐시에서 데이터 가져오기
System.out.println("key1: " + cache.get("key1"));
System.out.println("key2: " + cache.get("key2"));
// 메모리 부족 상황 시 가비지 컬렉터가 동작하면서 소프트 참조가 수거될 수 있음
System.gc();
// 가비지 컬렉터에 의해 수거된 데이터는 null이 반환됨
System.out.println("key1: " + cache.get("key1"));
System.out.println("key2: " + cache.get("key2"));
}
} 4**. 다중 스레드 처리** 스레드를 효율적으로 사용하고 관리하여 CPU 및 메모리 리소스를 효율적으로 활용해야 한다. 스레드가 너무 많거나 무한 대기 상태에 있다면 성능 문제가 발생할 수 있다. + GC 튜닝의 과정 간략하게… 일반적으로 GC 튜닝은 다음과 같은 단계를 거친다.
|
Beta Was this translation helpful? Give feedback.
-
시작하며세상에 벌써 책 한 권이 끝났다! 이번 이슈에서는 참고자료인 하나의 메모리 누수를 잡기까지 를 함께 살펴보며 주요 개념들을 다시 한 번 살펴볼 수 있도록 노력해보자! 에러 발생의 시작
모니터링 개선 SSARTEL-10th/JPTS_bookstudy#20그럼 이제 문제가 해결됐는가? (아니다) 그렇다면 우리가 어떤 부분을 놓치고 있었는지 제대로 확인해보기 위해서는 모니터링 개선이 필요하다. 모니터링 개선에 대해서는 지난 이슈들에서 다뤘던 로그와 모니터링 도구에 대해서 생각해보고자 한다.
문제 원인 파악그럼 이제 앞의 단계를 통해서 예외 처리나 서비스에서 제외된 장비나 쓰레드 등의 로그를 분석할 수 있다.
→ 하지만 이러한 방식은 서비스 중인 서버의 히스토그램을 확보해야 하는데, 서비스 중인 서버에서 받지 못했고, 숫자로는 접근이 어려울 수 있어 분석이 어려울 수 있다. jhat을 이용한다면 다음과 같이 class meta 정보와 생성된 객체 정보를 확인할 수 있다.
하지만 jhat의 경우 힙 덤프 파일이 크다면 분석이 어려울 수 있다는 단점이 있다.(아예 응답이 없을 수도 있음) (참고: https://soft.plusblog.co.kr/51) 이외에도 VisualVM을 통한 heap dump 분석에서는 프로세스가 진행 중에 Monitoring 에서 Heap Dump 버튼을 클릭하면 실제 Heap Dump 가 생기고 아래 그림과 같이 CPU, Memory, Classes, Thread를 확인할 수 있으며 Visual GC 플러그인을 통해 GC에 대한 현황을 볼 수 있다. 출처: https://liltdevs.tistory.com/167 각 클래스 별 정보도 확인할 수 있으며 비정상적으로 메모리를 많이 차지하고 있는 클래스에 대해서 먼저 접근해 원인을 확인해볼 수 있다. 문제 해결위의 과정을 통해서 문제가 발생한 Class 를 찾을 수 있었다. 지금부터는 내부 구현에서 어떤 부분에 문제가 있는지를 찾아야 한다.
이러한 문제 해결의 과정은 경험에 의해 학습하는 부분이 크다. 따라서 많은 사례를 접하고 적용해볼 수 있는 능력이 필요하다. 마무리이렇게 서비스에서 에러발생의 시작에서부터 해결까지 일련의 과정을 살펴보았다. 물론 성능테스트나 부하 테스트를 통해서 이러한 에러 발생을 미연에 방지할 수 있다면 좋겠지만(SSARTEL-10th/JPTS_bookstudy#21) 언제나 클라이언트는 개발자의 생각을 벗어날 수 있다. 그리고 그런 상황에서 성능 개선 및 트러블 슈팅에 대해서 지금까지의 학습 내용이 도움이 될 수 있으면 좋겠다! |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
All reactions