Language/Java

Java - 실수하기 쉬운 메모리 누수의 5가지 패턴!

JaeHoney 2022. 4. 15. 08:14

메모리 누수(Leak)

자바에서 메모리 누수는 더 이상 사용되지 않는 객체들이 GC(가비지 컬렉션)에 의해 소멸되지 않고 누적되는 현상입니다.

 

가비지 컬렉션의 소멸 대상이 되려면 다른 Reference 변수에서 참조하고 있지 않아야 합니다.

 

Miner GC

 

MinerGC는 Young 영역에 오래 되지 않은 객체를 사용이 끝나면 소멸합니다. 소요시간은 Young 영역의 크기에 따라 다르지만, 대체로 1초 미만입니다. JVM을 중지하지도 않고, 빠르고 효율적으로 동작합니다. 

 

객체가 사용이 끝나면 Miner GC에서 정리되는 것이 유리합니다.

 

Major GC

 

Miner GC에서 정리되지 못하고, 객체가 오래되면 Young영역에서 Old영역으로 이동됩니다. Major GC는 Old영역의 객체를 소멸하는 역할을 수행합니다.

 

Major GC는 객체의 사용 여부 판단을 위해서 모든 Thread를 멈추는 Stop-the-world를 실행해서, 참조 되지 않는 객체는 Heap에서 소멸합니다.

 

이 때 Mark & Sweep 알고리즘을 사용하게 되는데, Minor GC에 10배 이상을 사용하게 됩니다. (모든 Thread를 중지시키는 것 자체부터가...)

 

정리

 

즉, 짧은 주기의 객체는 사용이 끝나면 Minor GC로 정리 되고 Major GC는 지양하는 것이 Heap의 효율적인 관리가 됩니다.

 

하지만 객체를 다른 변수에서 참조를 하고 있으면 해당 객체는 Minor GC에서 정리되지 못하고, Old 영역으로 이동됩니다.

 

 

예시

1. Static 변수가 객체를 참조하고 있다면, 해당 객체는 GC에 의해 소멸되지 않습니다.

- Static 변수는 프로그램 종료 시점에 메모리가 반환되고, 사용하지 않아도 메모리를 점유하고 있습니다. 이런 static 변수를 재사용한다면 이점이 있지만, 사용하지 않으면 메모리를 점유하고 있게 됩니다. 

 

2. Stack에서 Heap에 있는 객체를 참조하고 있는 동안에는 해당 객체는 GC에 의해 소멸되지 않습니다.

 

메모리 누수가 발생하는 패턴들은 아래와 같습니다.

 

1. 무의미한 Wrapper 객체를 생성하는 경우

-> GC는 Immutable 객체를 Skip합니다. 컨테이너 자체가 사라질 때 같이 삭제합니다. 그래서 String은 StringBuilder와 달리 Immutable 객체이기 때문에 힙에 계속 쌓여서 메모리를 점유한다는 단점이 있습니다. Wrapper class의 객체는 모두 Immutable이기 때문에 조심해서 사용해야 합니다.

 

2. Map에 Immutable 데이터를 해제하지 않은 경우

Map에는 강력한 참조가 있어서, 내부 객체가 사용되지 않을 때도 GC 대상이 되지 않습니다. 즉, Map을 더이상 사용하지 않는다면, 메모리를 점유하고 있게 됩니다. 즉, 데이터의 메모리를 해제하는 것이 바람직합니다. WeakHashMap은 내부 데이터를 초기화할 수 있습니다.

 

3. Connection 사용 시 Try Catch 설계

아래의 경우 Connection을 생성했지만, try 내부에서 connection을 close하기 때문에, close가 실행되지 못합니다. 그래서 Connection이 열려있는 채로 메모리를 점유하게 됩니다.

try {
    Connection con = DriverManager.getConnection();
    // 예외 발생 !!!
    con.close();
} Catch(exception e) {
}

4. CustomKey 사용

Map을 사용할 때 custom key를 사용할 때는 equals()와 hashcode()를 값을 기반으로 구현해야합니다. 아래의 경우 Key값이 같은 객체로 인식하지 못해서 계속 Map에 쌓이게 되면서 메모리를 점유하게 됩니다.

public class CustomKey {
    private String name;
    
    public CustomKey(String name) {
        this.name=name;
    }
    
    public staticvoid main(String[] args) {
        Map<CustomKey,String> map = new HashMap<CustomKey,String>();
        map.put(new CustomKey("Shamik"), "Shamik Mitra");
   }
}

 

5. 더 이상 참조되지 않는 참조 (자료 구조 설계)

배열로 Stack을 구현한 예시입니다.

public class Stack {
       privateint maxSize;
       privateint[] stackArray;
       privateint pointer;
       public Stack(int s) {
              maxSize = s;
              stackArray = newint[maxSize];
              pointer = -1;
       }
       public void push(int j) {
              stackArray[++pointer] = j;
       }
       public int pop() {
              return stackArray[pointer--];
       }
       public int peek() {
              return stackArray[pointer];
       }
       publicboolean isEmpty() {
              return (pointer == -1);
       }
       public boolean isFull() {
              return (pointer == maxSize - 1);
       }
       public static void main(String[] args) {
              Stack stack = new Stack(1000);
              for(int ;i<1000;i++)
              {
                     stack.push(i);
              }
              for(int ;i<1000;i++)
              {
                     int element = stack.pop();
                     System.out.println("Poped element is "+ element);
              }
       }
}

pop을 할 때 포인터만 감소하고 실제 데이터는 그대로 남아있습니다. 알고리즘상 문제는 없지만, 배열에 해당 요소가 존재하기 때문에 GC의 대상이 될 수 없고, 사용하지 않는 요소들을 포함한 배열이 메모리에 남게 됩니다.

public int pop() {
    int size = pointer--;
    int element = stackArray[size];
    stackArray[size] = null;
    return element;
}

그래서 위의 경우 pop()을 할 때는 해당 배열 요소를 삭제해야 합니다.

 

GC 튜닝

 

자바를 실행할 때 GC 방식 / 메모리 크기를 지정할 수 있습니다. 이를 GC 튜닝이라고 합니다.

 

아래는 힙 영역의 구성을 정의해서 App을 Run하는 예제입니다.

 

java -Xms=256m -Xmx=1536m -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=2 App

  • -Xms, -Xmx [ Heap의 최소, 최대 Size ]
  • -XX:NewSize [ Young 영역의 초기 Size ]
  • -XX:MaxNewSize [ Young 영역의 최대 Size ]
  • -XX:NewRadio [ Young 영역 대비 Old 영역 Size의 비율 ]

 

 


 

 

Reference 1: https://dzone.com/articles/memory-leak-andjava-code
Reference 2: https://velog.io/@woo00oo/GC-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98Memory-Leak
Reference 3: https://www.holaxprogramming.com/2013/07/20/java-jvm-gc/