일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 프로그래밍
- 신입사원
- 운영체제
- github
- 뮤텍스
- 이펙티브 자바
- 스프링
- 알고리즘
- 자바
- 깃허브
- 세마포어
- package-private
- 공채
- 컴퓨터공학
- 메모리
- 컴퓨터과학
- 디지털
- spring
- 우리카드
- 개발
- CS
- IT
- 신입
- 정보처리기사
- java
- 깃
- Effective Java
- OS
- 스터디
- Public
- Today
- Total
주니어 개발자 성장기
아이템 7. 다 쓴 객체 참조를 해제하라. 본문
메모리 누수 발생을 주의하자
메모리 관리를 직접하는 경우 GC가 완벽하게 동작할 것을 기대하면 안된다 코드 내부에 더 이상 쓰이지 않지만 참조해제가 안된 객체들이 계속해서 존재할 가능성이 있다.
주요 원인
- Stack
- Cache
- Listener
공통점: 모두 내부에 객체를 쌓아두는 공간이 있다!
일반적으로 자기 메모리를 직접 관리하는 클래스라면 프로그래머는 항시 메모리 누수에 주의해야 한다.
- 이펙티브 자바 38p
대안
사용하지 않는 변수에 대해 명시적으로 null을 삽입해준다.
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
하지만, 이 방법도 바람직한 것은 아니라고 책에서는 다음과 같이 말한다.
객체 참조를 null 처리하는 일은 예외적인 경우여야 한다. 다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이다.
WeakHashMap을 사용한다.
일반 HashMap
은 키가 외부에서 참조하지 않아도 Map
내부에 존재 하므로 해당 Key가 GC의 대상이 되지 않지만, WeakHashMap
은 키가 외부에서 강하게 참조(Strong Reference)하는 곳이 없으면 바로 GC의 대상이 된다.
주의할 점! WeakReference
레퍼런스 종류
- Strong, Soft, Weak, Phantom
Soft Reference
Strong Reference가 없고 Soft Reference만 있으며 메모리가 필요할 경우에만 GC의 대상이 된다.
Phantom Reference
ReferenceQueue<T>
로 사용 가능하다. PhantomReference만 남을 경우 객체는 사용할 수 없지만 ReferenceQueue에 들어와 있다.
용도는?
- 자원 정리
- 언제 객체의 메모리가 해제되는 지 알 수 있다.
유의점
- 맵의 엔트리를 맵의 Value가 아니라 Key에 의존해야 하는 경우에 사용할 수 있다.
(Key가 없어질 때 Value가 없어져도 상관 없는 경우) Interger
같은 Primitive Type의 래퍼 클래스나String
타입의 경우 JVM 내부에 캐싱이 되므로 따라서, 이런 경우에는 레퍼런스하는 변수를 null로 바꿔도 지워지지 않는다.- 하지만, 이 방식은 자원이 언제 반납되는 지 애매하기 때문에 명시적으로 처리하는 편이 낫다. 따라서 Soft, Weak, Phantom 등은 극히 드물게 쓰인다.
백그라운드 스레드를 활용 한다.
- 백그라운드 스레드를 통해 애플리케이션이 동작하는 동안 더 이상 안쓰는 객체들을 정리할 수 있다.
ExecutorService
- 쓰레드를 만드는 작업은 생성비용이 매우 크다
- 그래서 쓰레드 풀을 만들어서 사용하는데,
ExecutorService
로 쓰레드 풀을 편리하게 만들 수 있다. 쓰레드의 개수를 매개 변수로 줄 수 있다.
ExecutorService service = Executors.newFixedThreadPool(10);
Future<String> submit = service.submit(new Task());
static class Task implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(2000L);
return Thread.currentThread() + " world";
}
- CPU-intensive한 작업은 쓰레드가 많아도 큰 효율을 볼 수 없다.
- 반면 IO-intensive한 작업은 쓰레드가 많을 때 효율적일 수 있다. → 적절한 쓰레드의 갯수는 직접 튜닝해봐야 알 수 있다.
Executors.newCachedThreadPool()
필요할 때마다 쓰레드를 생성하는 쓰레드 풀, 오래 사용되지않은 쓰레드는 제거된다. 쓰레드가 무한정 늘어날 수 있기 때문에 반드시 자원낭비를 유의해야 한다!Executors.newSingleThreadExecutor()
쓰레드 하나만을 사용하는 쓰레드 풀Executors.newScheduledThreadPool()
는 내부적으로 특이한 자료구조를 사용해서 스케쥴링을 감안해서 순서가 바뀔 수 있다.
예시
Runnable removeOldCache = () -> {
System.out.println("running removeOldCache task");
Map<CacheKey, Post> cache = postRepository.getCache();
Set<CacheKey> cacheKeys = cache.keySet();
Optional<CacheKey> key = cacheKeys.stream().min(Comparator.comparing(CacheKey::getCreated));
key.ifPresent((k) -> {
System.out.println("removing " + k);
cache.remove(k);
});
};
executor.scheduleAtFixedRate(removeOldCache, 1, 3, TimeUnit.SECONDS);
위와 같은 코드로 백그라운드 스레드에서 더 이상 사용하지 않는 객체를 정리할 수 있다.
LRU 캐시와 같은 특정한 자료구조를 사용한다.
캐시가 가득 찼을 때 가장 오랫동안 참조되지 않은 객체를 제거하는 자료구조로서 메모리 누수를 방지하기 위해 해당 자료구조를 사용할 수 있다.
정리
- 직접 메모리를 관리하는 경우 (Stack, Cache, Listener 등) 메모리 누수를 주의하자
- 여러가지 대안이 있으나 필요하지 않은 객체를
null
처리 하거나 백그라운드 스레드를 통해
원본 코드 출처: 이펙티브 자바
참조 및 코드 출처: 이펙티브 자바 완벽 공략 1부 - 백기선, 인프런
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템 9. try-finally 보다 try-with-resources를 사용하라. (0) | 2023.07.23 |
---|---|
아이템 8. finalizer와 cleaner 사용을 피하라 (0) | 2023.07.22 |
아이템 6. 불필요한 객체 생성을 피하라 (0) | 2023.07.17 |
아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. (0) | 2023.07.15 |
아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2023.07.13 |