ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 익명 클래스와 메모리 관계 알아보기 ( Anonymous Class , Method Area )
    Java 2024. 6. 2. 07:21
    728x90
    반응형

     

    - 목차

     

    들어가며.

    익명 클래스(Anonymous Class) 는 자바에서 이름 없는 클래스를 정의하고 동시에 인스턴스를 생성할 때 사용되는 문법입니다.

    익명 클래스간결함편의성을 제공하지만, 그 사용이 항상 메모리 효율적이지는 않습니다.

    익명 클래스의 일반적인 형태는 다음과 같습니다

     

    interface AnonymousInterface {
        void print();
    }
    
    abstract class AnonymousAbstract {
        abstract void print();
    }
    
    class Main {
        public static void main (String[] args) {
            AnonymousInterface instance = new AnonymousInterface() {
                public void print () {System.out.println("Anonymous Interface test");}
            };
            instance.print();
    
            AnonymousAbstract instance2 = new AnonymousAbstract() {
                public void print () {System.out.println("Anonymous Abstract test");}
            };
            instance2.print();
        }
    }

     

    • 익명 클래스는 Interface 와 Abstract Class 를 구현하는 형식으로 동작합니다.
    • 클래스 이름이 없으며, 정의와 동시에 인스턴스가 생성됩니다.

     

    여기서 중요한 포인트는 익명 클래스와 JVM 메모리 간의 관계를 이해하는 것입니다.

    자바에서 익명 클래스는 JVM 메모리 구조에서 Method AreaHeap 메모리에 각각 다른 방식으로 저장 및 관리됩니다.

    익명 클래스는 일반 클래스와 마찬가지로 클래스 메타데이터인스턴스 데이터를 구분하여 JVM 메모리에 저장됩니다.

     

    매번 익명 클래스를 통하여 인스턴스를 생성할 때마다 익명 클래스는 새로운 클래스 메타데이터로 취급되어 JVM Method Area 에 누적되는 점을 명심해야합니다.

    이 점이 간과된다면 메모리와 관련한 이슈가 발생할 수 있기 때문입니다.

     

    이어지는 내용에서 자세한 설명을 이어나가겠습니다.

     

    Method Area란 무엇인가 ?

    Method Area 는 JVM 메모리 구조의 한 부분으로, JVM이 실행 중인 애플리케이션에서 클래스와 인터페이스의 메타데이터를 저장하는 영역입니다.

    클래스 로더(Class Loader) 가 로드한 클래스의 구조와 관련된 모든 정보가 이 영역에 저장됩니다.

     

    클래스의 메타데이터는 아래와 같은 정보를 포함합니다.

     

    • 클래스 이름:
      • 클래스의 완전한 이름(Fully Qualified Name), 즉 패키지 경로를 포함한 클래스 이름이 저장됩니다.
      • 예: com.example.MyClass.
    • 필드 정보:
      • 클래스가 선언한 모든 필드(인스턴스 변수와 정적 변수 포함)의 이름, 데이터 타입, 접근 제어자 등이 저장됩니다.
      • 정적 변수는 Method Area에 저장되며, 클래스 수준에서 관리됩니다.
    • 메서드 정보:
      • 클래스의 메서드 이름, 반환 타입, 매개변수 목록, 바이트코드 등이 저장됩니다.
      • 메서드의 접근 제어자(public, private 등)와 특수 메서드(생성자, 정적 초기화 블록 등)도 포함됩니다.
    • 메서드 바이트코드:
      • 메서드의 실제 실행 코드는 바이트코드 형식으로 저장됩니다.
      • JVM은 이 바이트코드를 해석하거나 JIT 컴파일러를 통해 네이티브 코드로 변환합니다.

     

     

    왜 Method Area라고 불리는가 ?

    Method Area는 JVM에서 클래스의 메타데이터(Class Metadata)를 저장하는 공간임에도 불구하고,

    왜 Class Area 대신 Method Area라는 이름이 붙었을까요 ?

    이는 자바가 클래스 기반의 객체지향 언어라는 점과 깊은 관련이 있습니다.

     

    자바는 클래스 지향 언어로 설계되어, 모든 함수(메서드)는 반드시 클래스 내부에 속합니다.

    자바에서는 독립적인 함수가 존재하지 않으며, 모든 메서드는 클래스의 일부로 정의됩니다.

    따라서, JVM에서 클래스 메타데이터를 저장하는 공간의 핵심 목적은 클래스에 정의된 메서드 실행을 지원하는 데 있다고 볼 수 있습니다.

     

    클래스의 메타데이터들은 메소드 바이트코드를 포함하고 있고,

    실제 함수의 호출 시에는 Method Area 의 특정 Method 의 위치로 JUMP 하는 것을 알 수 있습니다.

    ( JVM 에 invokevirturl, invokespecial 와 같은 Instruction 이 지원됩니다. )

     

     

    익명 클래스와 메모리 관계.

    익명 클래스와 메모리의 관계에 대해서 설명해보도록 하겠습니다.

    익명 클래스는 Java 의 interface, class 를 new 키워드와 함께 사용할 수 있는 문법을 제공합니다.

    하지만 주목해야할 점은 익명 클래스는 new 키워드와 함께 사용될 때마다 Method Area 에 클래스가 로딩됩니다.

     

    첫번째 예시로 아래와 같이 interface 를 익명 클래스 방식을 활용하여 7개의 Instance 를 생성해보도록 하겠습니다.

    package com.westlife;
    
    interface AnonymousInterface { }
    
    class Main {
        public static void main (String[] args) throws InterruptedException {
            AnonymousInterface instance1 = new AnonymousInterface() {};
            AnonymousInterface instance2 = new AnonymousInterface() {};
            AnonymousInterface instance3 = new AnonymousInterface() {};
            AnonymousInterface instance4 = new AnonymousInterface() {};
            AnonymousInterface instance5 = new AnonymousInterface() {};
            AnonymousInterface instance6 = new AnonymousInterface() {};
            AnonymousInterface instance7 = new AnonymousInterface() {};
    
            while (true) Thread.sleep(10000);
        }
    }

     

    아래의 결과는 위 익명 클래스를 통해서 로딩된 클래스들의 목록을 VisualVM 을 통해서 확인한 결과입니다.

    아래의 사진과 같이 익명 클래스를 사용할 때마다 $숫자가 붙은 새로운 클래스들이 로드됩니다.

    이는 부모 클래스 + $숫자 와 같은 형식의 Naming Convention 을 가집니다.

     

     

     

    반복문 속 익명 클래스의 동작.

    반복문 내에서 익명 클래스가 여러 번 초기화되더라도, JVM의 최적화 덕분에 하나의 익명 클래스 정의만 Method Area에 로드됩니다.

    즉, 반복문이 실행될 때마다 새로운 익명 클래스가 생성되는 것처럼 보여도,

    실제로는 Method Area 에 이미 로드된 익명 클래스 정의를 재사용합니다.

     

    이로 인해 반복문 내에서 익명 클래스를 통한 다수의 초기화가 이루어지더라도, 메모리 사용량은 효율적으로 관리됩니다.

    이러한 동작은 JVM의 클래스 로딩 원칙과 캡처링 변수 처리 방식에 따라 결정됩니다.

    반복문 내에서 사용하는 캡처링 변수는 익명 클래스의 생성자를 통해 전달되며,

    클래스 정의 자체를 변경하지 않기 때문에 Method Area의 로드 횟수에 영향을 미치지 않습니다.

     

    결론적으로, 반복문 속에서 익명 클래스를 여러 번 초기화하더라도, JVM은 이를 최적화하여 Method Area에 익명 클래스를 한 번만 로드합니다.

     
    아래의 예시에서 반복문과 익명 클래스의 활용 예시입니다.
    package com.westlife;
    
    interface AnonymousInterface {
        void print();
    }
    
    class Main {
        public static void main (String[] args) throws InterruptedException {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                Thread.sleep(1000);
                int finalI = i;
                AnonymousInterface instance = new AnonymousInterface() {
                    public void print() {System.out.println(finalI);};
                };
                instance.print();
            }
            while (true) Thread.sleep(10000);
        }
    }

     

    • 반복문 변수 i 의 값을 캡처링하기 위해 finalI 로 복사합니다. 이 변수는 effectively final 로 간주됩니다.
    • 만약 final 또는 effectively final 변수를 사용하지 않는다면, 아래와 같은 컴파일 에러를 마주하게 됩니다.
    • error: local variables referenced from an inner class must be final or effectively final

    아래의 사진과 같이 오직 1개의 com.westlife.Main$1 익명 클래스만이 로드되게 됩니다.

     

     

     

     

    함수 내부에서 익명 클래스의 동작.

    함수 내부에서 사용되는 익명 클래스 역시 최적화되어 1개의 익명 클래스만이 Method Area 로 로드됩니다.

    즉, 반복문에서의 사용과 동일합니다.

     

    Main 클래스는 call 이라는 메소드를 가지고, call 메소드는 AnonymousInterface 의 객체를 생성합니다.

    당연히 익명 클래스의 초기화 방식을 사용하구요.

     

    package com.westlife;
    
    interface AnonymousInterface {
        void print();
    }
    
    class Main {
    
        private void call(int arg) {
            AnonymousInterface instance = new AnonymousInterface() {
                public void print() {System.out.println(arg);};
            };
            instance.print();
        }
    
        public static void main (String[] args) throws InterruptedException {
            Main main = new Main();
            for (int i = 1; i < Integer.MAX_VALUE; i++) {
                Thread.sleep(100);
                main.call(i);
            }
            while (true) Thread.sleep(10000);
        }
    }

     

    위 자바 코드의 실행 역시 최적화가 적용되어 1개의 익명 클래스만이 Method Area 로 로드되게 됩니다.

     

     

     

     

    람다 표현식으로 익명 클래스 구현하기.

    마지막으로 익명 클래스를 람다 표현식으로 구현하는 방법과 그 차이점을 알아보겠습니다.

     

    람다 표현식은 자바 8에서 도입된 기능으로,

    함수형 인터페이스(SAM) 의 단일 추상 메서드를 간결하게 구현할 수 있습니다.

    익명 클래스보다 코드가 훨씬 짧아지고, 함수형 프로그래밍 스타일에 더 가깝습니다.

     

    그리고 아래와 같은 방식으로 Interface 를 람다 표현식을 통해 객체 생성을 할 수 있습니다.

     

    package com.westlife;
    
    
    @FunctionalInterface
    interface AnonymousInterface {
        void print();
    }
    
    class Main {
    
        public static void main (String[] args) throws InterruptedException {
            AnonymousInterface instance1 = () -> {};
            AnonymousInterface instance2 = () -> {};
            AnonymousInterface instance3 = () -> {};
            AnonymousInterface instance4 = () -> {};
            AnonymousInterface instance5 = () -> {};
            AnonymousInterface instance6 = () -> {};
            AnonymousInterface instance7 = () -> {};
    
            System.out.println(instance1.getClass().getName());
            System.out.println(instance2.getClass().getName());
            System.out.println(instance1.getClass() == instance2.getClass()); // true (동일한 클래스)
    
            while (true) Thread.sleep(10000);
        }
    }

     

    람다 표현식을 사용하더라도 익명 클래스를 사용하는 것과 유사한 최적화를 보입니다.

     

     

     

     

    반응형

    'Java' 카테고리의 다른 글

    Java ReentrantLock 알아보기  (0) 2024.06.03
    Jackson 으로 JSON 다루기  (0) 2024.01.22
    Java off-heap 메모리 알아보기  (0) 2023.12.22
    Java Thread Pool 알아보기  (0) 2023.09.30
    Java Future 알아보기  (0) 2023.09.30
Designed by Tistory.