자바에서 가비지 컬렉터는 필요 없는 메모리를 관리해 주는 역할을 해줍니다.
하지만 가비지 컬렉터에게 모든 메모리 관리를 맡기면 안됩니다.
예를 보겠습니다.
😱 Stack 의 메모리 누수
Stack 클래스의 코드를 살펴보겠습니다
public class StackSample<T> {
private Object[] elements;
private int size = 0;
private static final intDEFAULT_INITIAL_CAPACITY= 16;
public StackSample() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e) {
ensureCapacity();
elements[size++]= e;
}
public Object pop() {
if(size == 0) {
throw new IllegalStateException();
}
return elements[--size];
}
public int getSize() {
return this.size;
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, size * 2 + 1);
}
}
}
위의 코드를 보면서 메모리 누수가 나는 곳을 살펴 보겠습니다. 먼저 Stack 의 first in last out 의 특징을 가지고 있습니다.
즉, ‘먼저 들어온 값이 나중에 나온다’ 라는 말입니다.
여기서 요소들을 추가할 때 push() 메서드를 사용합니다. 그리고 요소들이 추가 되면 size 는 늘어 나겠죠?
다음으로 우리는 pop() 메서드를 사용해 요소들을 추출하면 요소값만 반환되며 추출 되어 필요 없는 해당 요소는 그대로 있어 메모리를 차지할겁니다.
여기서 Stack 클래스는 요소를 넣기만 하고 제거는 안합니다. 이러면 메모리에 필요 없는 객체들이 차지 하고 있어 메모리 누수가 발생이 되겠죠??
코드를 수정하여 메모리 누수의 문제점을 해결해봅시다.
public Object pop() {
if(size == 0) {
throw new IllegalStateException();
}
Object element = elements[--size];
this.elements[size]= null; // 추출 한다음 해당 인덱스의 있는 값을 null 로 변경!
return element;
}
pop() 메서드를 수정하였습니다. 추출 후 해당 인덱스에 값을 null 로 변경하여 필요 없는 객체들을 관리 해주었습니다.
그렇다고 필요 없는 객체들을 다 null 로 바꾸어 주는건 안됩니다.
로컬 변수 같은 경우는 가비지 컬렉터가 관리하여 제거가 되는데 size 나 elements 같은 전역 변수 같은 경우 null 을 만들어 줘야 가비지 컬렉터가 관리 하므로 이렇게 직접 메모리를 관리 할 때 명시적으로 해당 값을 null로 변경한겁니다.
🤮 캐싱의 메모리 누수
먼저 캐싱(Caching) 부터 알아보겠습니다.
캐싱은 자주 사용 하는 데이터들을 캐시에 데이터를 미리 복사하여 데이터를 불러오는 시간을 절약해주는 행위를 말합니다.
캐싱의 형태는 엔트리의 구조로 Map 형태를 가지고 있습니다.
예제 코드를 먼저 보겠습니다.
public class CachingSample {
public static void main(String[]args) {
Map<Object, Object> cache = new HashMap<>();
Object o1 = new Object();
Object o2 = new Object();
cache.put(o1, o2);
}
}
Objcet 객체를 엔트리 구조로 가지는 HashMap 을 만들었습니다. 이때 우리는 Object 의 인스턴스를 생성하여 cache 에 put() 을 해줍니다.
그리고 Object 인스턴스가 더이상 필요하지 않는 경우라면 우리는 엔트리 구조를 제거 해야합니다.
@Test
@DisplayName("Hash Map Test")
void hash() {
// given
Map<String, String>cache = new HashMap<>();
String key1 = new String("spring");
String key2 = new String("spring boot"); // 손수 제거를 해야함!
// when & when
cache.put(key1, "java");
cache.put(key2, "java");
key1 = null;
System.gc();
Iterator<Map.Entry<String, String>> iterator = cache.entrySet().iterator();
cache.entrySet().stream().forEach(System.out::println);
// 실행 결과 : spring=java , spring boot=java
}
개발자가 손수 필요없는 객체들을 찾아서 제거 하려면 상당한 시간과 노력이 필요하겠죠?
하지만 WeakHashMap 을 사용하면 가비지 컬렉터에 의해 자동으로 필요없는 엔트리를 제거해줍니다.
@Test
@DisplayName("WeakHashMap Test")
void weakHash() {
// given
WeakHashMap<String, String> cache = new WeakHashMap<>();
String key1 = new String("spring");
String key2 = new String("spring boot");
// when & when
cache.put(key1, "java");
cache.put(key2, "java");
key1 = null;
System.gc();
Iterator<Map.Entry<String, String>>iterator = cache.entrySet().iterator();
cache.entrySet().stream().forEach(System.out::println);
// 실행 결과 : spring boot=java
참조 : https://yhmane.tistory.com/152
🥶 콜백의 메모리 누수
콜백을 하는 경우 계속해서 콜백이 메모리에 쌓일 거고 필요 없는 콜백이 제거가 안된다면 메모리의 누수가 발생됩니다.
이때도 WeakHashMap 을 사용하면 가비지 컬렉터에 의해 쌓인 콜백 중 필요없는 콜백들을 자동으로 제거합니다.
'Book > Effective Java' 카테고리의 다른 글
[ITEM 11] equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2022.07.10 |
---|---|
[ITEM 10] equals는 일반 규약을 지켜 재정의하라 (0) | 2022.07.10 |
[Book] ITEM 9) try-finally 대신 try-with-resource 를 사용하라 (0) | 2022.03.19 |
[Book] ITEM 8) finalizer 와 cleaner는 피하라 (0) | 2022.03.07 |
[Book] ITEM 6) 불필요한 객체는 만들지 말자 (0) | 2022.03.03 |
[Book] ITEM 5) 리소스를 엮을 때는 의존성 주입을 선호하라 (0) | 2022.03.03 |
[Book] ITEM 4) private 생성자로 noninstantiability를 강제할 것 (0) | 2022.03.01 |
[Book] ITEM 3) private 생성자 또는 enum 타입을 사용해서 싱글톤으로 만들것 (0) | 2022.03.01 |