자바의 내부 클래스(Inner Class)와 메모리 누수(memory leak)

서버란·2024년 9월 7일

자바 궁금증

목록 보기
14/35

내부 클래스와 외부 클래스의 관계, 특히 비정적(non-static) 내부 클래스가 어떻게 동작하는지에 대한 이해가 중요합니다. 이를 설명하면서 메모리 누수와 관련된 문제도 함께 다루겠습니다.

  1. 내부 클래스(Inner Class)와 외부 클래스의 관계
    내부 클래스는 외부 클래스의 멤버 변수나 메서드에 접근할 수 있는 특별한 클래스입니다. 내부 클래스는 크게 두 가지로 나뉩니다:
  • 정적 내부 클래스(static inner class)
  • 비정적 내부 클래스(non-static inner class)
    비정적 내부 클래스
    비정적 내부 클래스는 외부 클래스의 인스턴스에 암묵적으로 참조를 가지게 됩니다. 즉, 내부 클래스 객체가 생성되면, 외부 클래스의 인스턴스를 참조하고 그 인스턴스의 필드와 메서드에 접근할 수 있습니다.
  1. 메모리 누수의 발생 원인
    비정적 내부 클래스는 외부 클래스의 인스턴스에 대한 참조를 가지고 있으므로, 내부 클래스 객체가 존재하는 동안 외부 클래스의 인스턴스도 GC(Garbage Collection)에 의해 수거되지 않습니다. 이로 인해 외부 클래스 객체가 필요하지 않음에도 불구하고 메모리에 남아 있게 되어, 결과적으로 메모리 누수(memory leak)가 발생할 수 있습니다.

예를 들어:

class OuterClass {
    private String data = "Outer class data";
    
    class InnerClass {
        public void printData() {
            System.out.println(data);  // 외부 클래스의 필드에 접근 가능
        }
    }
}

위 코드에서 InnerClass는 OuterClass의 인스턴스 필드인 data에 접근할 수 있습니다. 즉, InnerClass 객체는 항상 OuterClass 객체에 대한 참조를 암묵적으로 가지고 있게 됩니다.

만약 외부 클래스 객체가 더 이상 필요 없다고 해도, 내부 클래스 객체가 여전히 참조 중인 경우, 외부 클래스 인스턴스는 가비지 컬렉터에 의해 수거되지 않고 메모리에 남아 있을 수 있습니다. 이 경우, 메모리 누수가 발생할 수 있습니다.

  1. 메모리 누수 방지 방법
    비정적 내부 클래스가 외부 클래스의 참조를 유지하는 문제를 방지하기 위한 몇 가지 방법이 있습니다.

1) 정적 내부 클래스(static inner class) 사용
정적 내부 클래스는 외부 클래스의 인스턴스에 대한 참조를 가지지 않기 때문에, 메모리 누수 문제가 발생하지 않습니다.

class OuterClass {
    static class StaticInnerClass {
        // 외부 클래스의 인스턴스 멤버에 접근 불가
    }
}

정적 내부 클래스는 외부 클래스의 정적 멤버(static field, static method)에는 접근할 수 있지만, 인스턴스 필드나 메서드에는 접근할 수 없습니다. 외부 클래스의 인스턴스 참조가 필요 없을 때 유용합니다.

2) 외부 클래스의 약한 참조(Weak Reference) 사용
내부 클래스가 외부 클래스를 참조하지 않도록 하기 위해, 약한 참조(WeakReference)를 사용하여 외부 클래스 인스턴스에 대한 참조를 끊을 수 있습니다.

class OuterClass {
    private String data = "Outer class data";
    
    class InnerClass {
        private WeakReference<OuterClass> outer;
        
        InnerClass(OuterClass outerInstance) {
            this.outer = new WeakReference<>(outerInstance);
        }

        public void printData() {
            OuterClass outerInstance = outer.get();
            if (outerInstance != null) {
                System.out.println(outerInstance.data);
            }
        }
    }
}

이 방법은 외부 클래스 객체에 대한 강한 참조를 사용하지 않으므로, GC가 외부 클래스 객체를 더 쉽게 수거할 수 있게 도와줍니다.

  1. 결론
  • 비정적 내부 클래스는 외부 클래스의 인스턴스를 암묵적으로 참조하므로, 내부 클래스 객체가 남아 있는 한 외부 클래스의 인스턴스가 가비지 컬렉션되지 않아서 메모리 누수가 발생할 수 있습니다.
  • 이를 방지하려면 정적 내부 클래스를 사용하거나, 약한 참조(Weak Reference)를 활용하는 방법이 있습니다.
  • 비정적 내부 클래스를 사용할 때는 이러한 메모리 관리 문제를 신경 써야 합니다.

Q1: 비정적 내부 클래스가 왜 외부 클래스의 참조를 가지는지, 그 동작 원리를 더 구체적으로 설명할 수 있나요?

Q2: 정적 내부 클래스의 장단점은 무엇인가요? 언제 비정적 내부 클래스 대신 사용하는 것이 좋을까요?

Q3: 자바에서 약한 참조(WeakReference) 외에도 메모리 누수를 방지하기 위한 다른 기법들은 어떤 것들이 있을까요?

답:

Q1: 비정적 내부 클래스가 왜 외부 클래스의 참조를 가지는지, 그 동작 원리를 더 구체적으로 설명할 수 있나요?
비정적 내부 클래스는 외부 클래스의 인스턴스 필드와 메서드에 접근할 수 있도록 설계되어 있기 때문에, 내부 클래스는 외부 클래스의 인스턴스에 대한 암묵적인 참조를 가집니다. 이를 가능하게 하는 동작 원리는 다음과 같습니다:

비정적 내부 클래스가 생성될 때, 자바는 내부 클래스 객체에 외부 클래스의 인스턴스에 대한 참조를 함께 저장합니다. 이 참조는 내부 클래스의 모든 메서드나 필드에서 외부 클래스의 인스턴스 멤버에 접근할 수 있도록 해 줍니다.
예를 들어:

OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();

여기서 InnerClass는 OuterClass의 인스턴스 outer에 대한 참조를 암묵적으로 가지게 되어, InnerClass 객체는 언제든지 outer 객체의 필드와 메서드에 접근할 수 있습니다. 이 때문에, 내부 클래스 객체가 존재하는 한 외부 클래스의 인스턴스도 계속 메모리에 남아 있을 수 있습니다.

Q2: 정적 내부 클래스의 장단점은 무엇인가요? 언제 비정적 내부 클래스 대신 사용하는 것이 좋을까요?

장점:

  • 외부 클래스 인스턴스 참조가 없음: 정적 내부 클래스는 외부 클래스의 인스턴스에 대한 참조를 가지지 않으므로, 메모리 누수 문제가 발생하지 않습니다. 이로 인해 메모리 관리가 더 효율적입니다.
  • 캡슐화: 외부 클래스 내부에 정의됨으로써 외부 클래스와 연관된 코드를 더 가독성 있게 관리할 수 있습니다. 코드가 잘 구조화되고 명확해집니다.
  • 독립성: 외부 클래스 인스턴스와 무관하게 정적 내부 클래스는 독립적으로 생성 및 사용될 수 있습니다.

단점:

  • 외부 클래스의 인스턴스 멤버에 접근 불가: 정적 내부 클래스는 외부 클래스의 인스턴스 멤버에 접근할 수 없습니다. 만약 외부 클래스의 인스턴스 필드나 메서드에 접근해야 한다면, 비정적 내부 클래스를 사용해야 합니다.
  • 언제 사용하는 것이 좋은가?
    외부 클래스의 인스턴스와 독립적으로 동작할 수 있는 도우미(helper) 클래스를 정의할 때.
    외부 클래스의 인스턴스 상태와 상관없이 내부 클래스가 특정 기능을 수행할 때.
    메모리 관리가 중요한 경우, 특히 메모리 누수를 방지해야 하는 상황에서 정적 내부 클래스를 사용하는 것이 좋습니다.

Q3: 자바에서 약한 참조(WeakReference) 외에도 메모리 누수를 방지하기 위한 다른 기법들은 어떤 것들이 있을까요?

  • 캐시 관리: 자바에서는 캐시 객체를 오래 유지하면 메모리 누수가 발생할 수 있습니다. 이를 방지하기 위해 WeakHashMap을 사용하면, 키에 대해 약한 참조를 사용하여 불필요해진 객체를 자동으로 제거할 수 있습니다.
Map<Key, Value> cache = new WeakHashMap<>();
  • 리스너 제거: 이벤트 리스너나 콜백이 등록된 상태로 남아 있으면, 해당 리스너가 외부 객체에 대한 참조를 유지해 가비지 컬렉션이 불가능할 수 있습니다. 이를 방지하기 위해 사용 후 반드시 리스너를 제거하는 습관을 들여야 합니다.
someComponent.removeListener(listener);
  • 자동 리소스 관리(Try-with-resources): 자바 7부터 제공되는 try-with-resources 문법을 통해, I/O 리소스나 데이터베이스 연결과 같은 자원을 자동으로 닫을 수 있습니다. 이는 자원이 제대로 해제되지 않아 발생하는 메모리 누수를 방지합니다.
try (BufferedReader br = new BufferedReader(new FileReader("file.txt"))) {
    // 파일 처리
}  // 자동으로 리소스가 닫힘
  • 직접적인 메모리 관리: 자바에서 dispose()와 같은 메서드를 통해 사용이 끝난 객체의 자원을 직접 해제하는 방식도 메모리 누수를 방지하는 방법입니다. 특히 GUI 애플리케이션에서 사용되는 객체들은 명시적으로 해제해야 할 필요가 있습니다.
profile
백엔드에서 서버엔지니어가 된 사람

0개의 댓글