SSARTEL-10th / JPTS_bookstudy

"개발자가 반드시 알아야 할 자바 성능 튜닝 이야기" 완전 정복
7 stars 0 forks source link

GC 튜닝은 제일 마지막에! #22

Open daminzzi opened 9 months ago

daminzzi commented 9 months ago

👍 문제

교재 19장에서 우리는 이런 소제목을 만났다. "GC 튜닝을 꼭 해야 할까?" 사실 JAVA에서 GC를 잘 만들어놨으니까 GC의 문제보다는 개발 로직 상의 문제가 클 것이다. 그렇다면 GC 튜닝 전 어떤 것들을 먼저 확인하고 최적화 해 봐야 할까? +. 내용이 많지 않다면 선물로 GC 튜닝 과정(p371~ 377)에 대해서도 정리해보자

✈️ 선정 배경

사실 개발과정에서 옵션을 설정하는 단계의 GC 튜닝을 하기보다는 그 전의 단계에서 코드 자체를 수정하거나 하는 일이 더 많지 않을까 하는 생각이 들었습니다. 그래서 최적화 과정을 다시 한 번 정리해보면서 개발 과정에서의 유의점을 정리해두면 좋을 것 같습니다.

📺 관련 챕터 및 레퍼런스

ch19. GC 튜닝을 항상 할 필요는 없다.

🐳 비고

교재에 해당 내용이 잘 정리되어 있으니 교재 요약을 위주로 정리해도 좋을 것 같습니다!

Yg-Hong commented 9 months ago

서론

저자의 말을 빌려보면 GC가 필요한 상황은 JVM의 메모리 크기도 지정하지 않았고, Timeout이 지속적으로 발생하고 있는 상황이다. Untitled (5)

우리는 이 상황을 조금 더 자세히 들여다 볼 것이다.

본론

사실, 지금부터 말할 내용은 우리가 지난 4주간 피땀흘려 공부한 내용을 종합하는 것이다.

1. memory leak 식별 및 해결

memory leak이 발생하는 상황은 잦은 GC를 유발하는 대표적인 이유이다. 이슈 16([#16])_메모리 릭(Memory leak)과 GC에서 이미 다루었듯 더 이상 불필요한 메모리가 GC에 의해 해제되지 않으면서 메모리 할당을 잘못 관리된다면 이는 다시 GC를 유발하는 악순환에 빠지게 된다. 이슈 16([#16])에서도 공부했듯 메모리 릭의 주된 원인은 무분별한 Autoboxing으로 인한 불필요한 참조 증가, 맵에 캐쉬 데이터를 선언하고 해제하지 않아 불필요한 참조 증가, java.sql.Connection 객체와 같이 스트림 객체를 사용하고 닫지 않는 경우 등에 의해 발생한다. 따라서 우리는 메모리 프로파일링 도구를 사용하여 누수를 식별하고, 코드를 검토하여 불필요한 객체 참조를 제거해야한다. 아래는 2015년 기준 대표적으로 사용하는 자바 메모리 프로파일링 도구의 도식표이다 Untitled (6)

2. 불필요한 객체 생성 줄이기 Untitled (7) 책에도 이미 살짝 언급한 내용이 존재한다. 비슷한 원리로 GC를 줄이는 방식이 무엇이 있을까 고민해보자.

List<Integer> list; // 루프 밖에서 선언

for (int i = 0; i < 100_000_000; i++) {
    list = new ArrayList<>();

    /* list 사용 */

    list = null; // 사용 후에 참조 해제
}

3. 캐시 활용

자주 사용하는 데이터를 캐시하여 반복적인 계산이나 sql 쿼리를 피할 수 있다. 캐시가 사용되는 가장 기본적인 이유 중 하나가 메모리 릭을 막는 방법과 아주 깊은 연관이 있다. 캐시를 사용하여 반복적으로 계산하거나 데이터를 다시 불러오는 대신 이전 결과를 저장하고 다시 사용함으로써 불필요한 메모리 소비를 방지할 수 있다. 예시를 멀리서 찾지 말자. 피보나치 수열을 dp로 풀던 경험을 떠올리면 익숙할 것이다. 위에선 캐시를 조심하라며, 근데 또 캐시를 활용하라 해? 뭐 어쩌라는거야? Untitled (8) 잘 좀 쓰자. 맵에 데이터를 캐시하고 방치하는 것은 당연히 메모리 누수를 유발하지만 이를 방지하기 위한 적절한 메커니즘을 사용해야 한다.

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();
}

}


```java
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 튜닝은 다음과 같은 단계를 거친다.

  1. 성능 목표 설정
  2. GC 로그 분석
  3. 메모리 사용 분석
  4. GC 튜닝 옵션 설정
  5. 메모리 할당 및 객체 라이프사이클 관리
  6. GC 이벤트 최적화
  7. 테스트 및 모니터링
  8. 반복 및 개선