다 쓴 객체 참조에 항상 null 값을 할당해야 하나요?

주싱·2023년 1월 12일
0

더 나은 코드

목록 보기
5/14

이런 코드가 있네?

어제 팀에서 작성된 코드 중에 이런 패턴의 코드가 있어서 Effective Java 책의 내용(아이템 7 다 쓴 객체 참조를 해제하라)을 살펴보았습니다.

public void command(String jsonCommand) {
    ObjectMapper mapper = new ObjectMapper();
    List<String> commands = mapper.readValue(jsonCommand, new TypeReference<>() {});
    mapper = null;

    ... 
}

그럴 필요는 없다

결론은 ‘다 쓴 객체 참조를 프로그래머가 항상 명시적으로 해제(null 처리)할 필요는 없다’ 입니다. 아래는 관련된 책의 내용을 인용한 것입니다.

“이 문제로 크게 데인 적이 있는 프로그래머는 모든 객체를 다 쓰자마자 일일이 null 처리하는 데 혈안이 되기도 한다. 하지만 그럴 필요도 없고 바람직하지도 않다. 프로그램을 필요 이상으로 지저분하게 만들 뿐이다. 객체 참조를 null 처리하는 일은 예외적인 경우여야 한다.”

이유 역시 책에서 설명합니다. 참조를 담고 있는 변수가 유효 범위(scope)를 벗어나면 자연스레 참조가 해제된다고 합니다. 아래 역시 관련된 책의 내용을 인용한 것입니다.

“다 쓴 참조를 해제하는 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위(scope) 밖으로 밀어내는 것이다. 여러분이 변수의 범위를 최소가 되게 정의했다면(아이템 57) 이 일은 자연스럽게 이뤄진다.”

그럼 언제 필요해?

책에서 설명하는 null 처리가 필요한 예외적인 경우는 Stack 구현 예시 코드와 같이 자기 메모리를 직접 관리하는 클래스를 사용할 때입니다. Stack을 구현한 배열에서 pop을 통해 마지막 index의 참조를 꺼낸다면 해당 index 영역은 더 이상 사용하지 않음에도 객체의 참조를 보관하고 있게 됨으로 반드시 null 처리를 해주어야 한다는 겁니다. 그렇지 않으면 스택이 커졌다가 줄어들 때 스택에서 꺼내진 객체들을 가비지 컬렉터가 회수하지 못하게 되고 그 객체가 참조하던 모든 다른 객체까지 회수하지 못해 메모리 누수가 발생한다고 설명합니다.

public class Stack {
		private Object[] elements;
		priviate int size = 0;

		public void push(Object e) {
				ensureCapacity();
				elements[size++] = e;
		}

		public Object pop() {
				if (size == 0) 
						throw new EmptyStackException();
				Object result = elements[--size];
				elements[size] = null; // 다 쓴 참조 해제
				return result;
		}

		private void ensureCapacity() {
				if (elements.length == size) 
						elements = Arrays.copyOf(elements, 2 * size +1);
		}
}

Java ArrayList 구현 클래스의 remove 메서드를 보아도 같은 맥락의 null 처리 코드를 볼 수 있습니다.

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
		transient Object[] elementData;
    ... 
    public boolean remove(Object o) {
				Object[] es = elementData;
        ... 
        fastRemove(es, i);
        return true;
    }

    private void fastRemove(Object[] es, int i) {
        modCount++;
        final int newSize;
        if ((newSize = size - 1) > i)
            System.arraycopy(es, i + 1, es, i, newSize - i);
        es[size = newSize] = null;
    }

마치며

그 동안 이런 내용을 정확히 구분해서 알고 있지 못했던 것 같습니다. 동료들의 코드와 설명 덕분에 더 자세히 찾아보고 정확히 이해할 수 있어서 좋았습니다. 그리고 정확히 이해하는 것이 얼마나 중요한지 다시 한 번 느낍니다. 올해 이펙티브 자바 책을 꼭 다 읽어봐야 겠습니다.

profile
소프트웨어 엔지니어, 일상

0개의 댓글