Spring/ETC

자바 메모리 누수 원인

YoonJong 2023. 3. 8. 23:36
728x90

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

 

정리가 되지 않으면, 누적된 객체로 인해 프로그램의 응답속도가 늦어지면서 성능저하 및 프로그램이 뻗어버릴 수 있습니다. ( Out Of Memory )

 

가비지 컬렉션의 동작하는 순서는 간단히 말하면,

Young(Minor GC) 영역과 Old(Major GC) 영역을 통해 관리됩니다.

Young 영역에서 대부분의 객체가 접근 불가능한 상태가 되기 때문에, 많은 객체들이 해당 영역에서 소멸됩니다.

Young 영역에서 지속적으로 살아남은 객체들은 Old 영역으로 이동됩니다.

 

가비지 컬렉션을 실행하기 위해 Stop the World 라는 작업과,

사용 여부를 판단하고 메모리를 해제하는 작업인 Mark and Sweep 작업은 큰 부하가 걸립니다.

따라서, 메모리를 효율적으로 사용하기 위해서는 Young 영역에서 객체를 빠르게 정리하는 것이 유리합니다.

가비지 컬렉션이 작동하기 위해서는 해당 객체는 어느 곳에서든 참조가 되지 않아야 합니다.

 

가비지 컬렉션이 작동하지 않는 원인을 알아보겠습니다.

아래 사이트에서 자세한 내용을 확인할 수 있습니다.

https://dzone.com/articles/memory-leak-andjava-code

 

Memory Leaks and Java Code - DZone

 

dzone.com

 

1. static 변수에 의한 참조
- static 변수는 인스턴스 변수처럼 new 될때가 아닌, 프로세스가 메모리에 로드 되는 순간 정적변수 영역(data 영역)에 생성되고 GC 에 의해 삭제되지 않습니다.
- 프로그램이 종료되어야 반환되는 성격을 가지고 있습니다.

 

2. 래퍼 클래스 (Long 등) 을 이용해서, 무의미한 객체를 생성 하는 경우

래퍼 클래스는 원시타입(Primitive) 타입보다 많은 메모리를 사용합니다.

아래 코드에서는 1000개의 원시타입을 생성하게 됩니다.

최대한 원시타입을 사용하는 것이 메모리의 효율성에 도움을 줄 수 있습니다.

public class Adder {
       publiclong addIncremental(long l)
       {
              Long sum=0L;
               sum =sum+l;
               return sum;
       }
       public static void main(String[] args) {
              Adder adder = new Adder();
              for(long ;i<1000;i++)
              {
                     adder.addIncremental(i);
              }
       }
}

 

3. 캐시 사용

아래 코드에서는 캐시를 사용하고 있지만, 지우는 코드가 없습니다.

캐시의 항목이 더 이상 필요하지 않으면 잊지 말고 지우는 것이 필요합니다.

public class Cache {
       private Map<String,String> map= new HashMap<String,String>();
       publicvoid initCache()
       {
              map.put("Anil", "Work as Engineer");
              map.put("Shamik", "Work as Java Engineer");
              map.put("Ram", "Work as Doctor");
       }
       public Map<String,String> getCache()
       {
              return map;
       }
       publicvoid forEachDisplay()
       {
              for(String key : map.keySet())
              {
                String val = map.get(key);                 
                System.out.println(key + " :: "+ val);
              }
       }
       public static void main(String[] args) {            
              Cache cache = new Cache();
              cache.initCache();
              cache.forEachDisplay();
       }
}

 

4. 연결이 종료되지 않을 때

아래의 코드에서 예외가 발생하게 된다면 con.close() 가 실행되지 않아 열려있는 상태가 됩니다.

이 경우에는 풀로 다시 돌아갈 수 없게되어 메모리 누수가 발생합니다.

try
{
  Connection con = DriverManager.getConnection();
  …………………..
    con.close();
}

Catch(exception ex)
{
}

 

5. equals() 와 HashCode() 구현을 확인

map.get 메서드가 hashCode() 와 Equal() 을 확인하는데, 구현이 되어있지 않아 저장된 키와 값을 검색할 수 없습니다.

public class CustomKey {
       public CustomKey(String name)
       {
              this.name=name;
       }
       private String name;
       publicstaticvoid main(String[] args) {
              Map<CustomKey,String> map = new HashMap<CustomKey,String>();
              map.put(new CustomKey("Shamik"), "Shamik Mitra");
              String val = map.get(new CustomKey("Shamik"));
              System.out.println("Missing equals and hascode so value is not accessible from Map " + val);
       }
}

 

728x90