본문 바로가기

STUDY/이펙티브자바

2-7) 다 쓴 객체 참조를 해제하라

메모리 할당과 해제

 

int * int_array = new int[10];

c++ 에서는 new 키워드를 통해, 메모리를 할당 할 수 있다. 위의 코드에서는 길이 10의 int형 배열을 위한 메모리 공간을 new 키워드로 할당하고 그 메모리 주소를 int_array 변수에 할당하였다.

 

int * makeIntArray(){
    int * int_array = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    return int_array;
}

// 에러 또는 잘못된 값에 접근
int * int_array = makeIntArray();
int num1 = int_array[0];

그렇다면 굳이 new를 사용하여 메모리를 할당해야 하는 이유는 무엇일까? 그것은 바로 new를 사용해야 프로그램의 runtime동안 사용할 수 있는 힙메모리에 데이터가 저장되기 때문이다. 위의 makeIntArray 코드는 new 없이 배열을 만들었기 때문에 그 데이터는 스택메모리에 저장된다. 스택메모리는 해당 변수가 생성된 스코프에서 벗어나면 해당 데이터가 사라진다. 따라서 해당 메소드가 끝나면서 int_array에 넣어진 데이터는 사라지고, makeIntArray()의 반환값을 사용하여 데이터에 접근하려고 하면 에러가 발생하거나 잘못된 값에 접근하게된다. 따라서 프로그램이 실행되는 동안 계속 사용되어야하는 데이터의 경우 new로 자동으로 사라지지않게 힙메모리에 할당해야 한다.

 

int * int_array1 = new int[10];
int * int_array2 = new int[10];
int * int_array3 = new int[10];
...
delete int_array1;
delete int_array2;
delete int_array3;

프로그램이 실행되는 동안 데이터가 사라지지않는 특성 때문에 new의 사용은 조심해야한다. 메모리에 데이터가 사라지지않고 계속 쌓이기만 한다면, 어느 순간 "out of memory" 메모리 부족 에러를 보게된다. 따라서 C++에서는 사용가치가 끝난 메모리는 delete 키워드로 해제를 해주어야 한다.

 

 


Java GC(Garbage Collection)

 

C++처럼 메모리의 할당과 해제를 개발자에게 맡기면 개발자로서는 부담이 늘어나고 프로그램의 메모리 누수로 인한 장애의 가능성도 늘어난다. 따라서 Java는 메모리의 해제를 개발자에게 맡기지 않고 스스로 GC를 수행한다. Java GC는 JDK별로 다양한 동작방식을 가지고 있지만, 더이상 사용되지않는 메모리공간을 찾아서 자동으로 해제한다는 점에서 같은 역할을 수행한다. Java GC 덕분에 개발자는 부담을 덜어내고 프로그램의 메모리 누수 위험도 줄어들었다.

 

 


메모리 누수를 찾아보아라

 

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

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

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }
    ...
 }

Java GC는 자동으로 사용되지않는 메모리를 탐지하여 해제하지만, 위의 코드는 Java GC를 속여서 실제로 사용되지 않는 메모리를 탐지하지 못하도록 하고있다.

 

Stack stk = new Stack();
stk.push(object);              // elements[0]에 object가 들어간다.
stk.pop();                     // 여전히 elements[0]에 object가 들어가있다.

위의 코드를 실행하면 object객체는 더이상 사용되는 곳이 없지만 elements[0]에 여전히 담겨있기 때문에 GC는 이를 탐지하지 못한다.

 

...
    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }
...

 

Stack stk = new Stack();
stk.push(object);              // elements[0]에 object가 들어간다.
stk.pop();                     // 여전히 elements[0]가 null이고 아무곳에서도 object를 사용하고 있지 않기때문에, GC가 해당 메모리를 해제한다.

이렇게 pop()한 데이터를 elements가 더이상 사용하지 않도록 해야지 GC가 메모리를 해제하여 메모리 누수가 발생하지 않는다.

 

 


그 외의 메모리 누수

 

 - https://www.baeldung.com/java-memory-leaks

728x90