Develop/JAVA

[Java] 메모리 관리 및 가비지 컬렉션: 개념과 예제

issuemaker99 2024. 11. 17. 15:09
728x90

Java는 개발자가 직접 메모리를 관리하지 않고, JVM(Java Virtual Machine)이 메모리를 자동으로 관리합니다. 이를 통해 메모리 누수(Memory Leak)와 같은 문제를 줄이고 안정적인 프로그램을 개발할 수 있습니다. Java의 메모리 관리와 가비지 컬렉션에 대한 구체적인 개념과 동작 원리를 이해하면, 효율적인 코드 작성과 최적화를 도울 수 있습니다.


Java 메모리 구조

Java의 메모리는 크게 다음과 같은 영역으로 나뉩니다:

  1. Heap
    • 객체와 인스턴스 변수들이 저장됩니다.
    • GC(Garbage Collector)에 의해 관리됩니다.
    • Young Generation, Old Generation, 그리고 Permanent Generation/Metaspace로 나뉩니다.
  2. Stack
    • 각 스레드에서 실행 중인 메서드 호출과 관련된 로컬 변수와 메서드 호출 스택이 저장됩니다.
    • 메모리가 메서드 호출이 끝나면 자동으로 해제됩니다.
  3. Method Area
    • 클래스 메타데이터, 정적 변수, 그리고 메서드 정보가 저장됩니다.
    • Java 8 이후에는 Metaspace라는 이름으로 Heap 바깥에 위치합니다.
  4. PC Register 및 Native Method Stack
    • 각각 스레드의 현재 실행 중인 명령과 네이티브 메서드 호출 정보를 저장합니다.

가비지 컬렉션(Garbage Collection)이란?

Java의 **가비지 컬렉션(GC)**은 더 이상 참조되지 않는 객체를 찾아 Heap 메모리에서 제거하는 프로세스입니다. 개발자가 직접 메모리를 해제하지 않아도, GC가 자동으로 이를 처리하여 메모리 누수를 방지합니다.


가비지 컬렉션의 동작 원리

  1. Mark-and-Sweep 알고리즘
    GC는 "더 이상 참조되지 않는 객체"를 탐색하는 Mark 단계와, 탐색된 객체를 제거하는 Sweep 단계로 나뉩니다.
  2. Generational Garbage Collection
    • Young Generation: 새롭게 생성된 객체가 저장되는 영역.
      • Eden과 Survivor 영역으로 나뉩니다.
    • Old Generation: 오래 살아남은 객체가 저장됩니다.
    • Permanent Generation/Metaspace: 클래스 정보와 상수 풀(Constant Pool)이 저장됩니다.
  3. Stop-the-World 이벤트
    GC가 실행되면 모든 애플리케이션 스레드가 잠시 멈추는 이벤트가 발생합니다. 이는 성능 문제를 야기할 수 있으므로 GC 튜닝이 중요합니다.

예제: 가비지 컬렉션 시뮬레이션

다음은 Java에서 가비지 컬렉션이 작동하는 방식을 시뮬레이션한 간단한 예제입니다.

public class GarbageCollectionExample {
    static class MyObject {
        private String name;

        public MyObject(String name) {
            this.name = name;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("Garbage collected for object: " + name);
        }
    }

    public static void main(String[] args) {
        // 객체 생성
        MyObject obj1 = new MyObject("Object 1");
        MyObject obj2 = new MyObject("Object 2");

        // obj1을 null로 만들어 참조 제거
        obj1 = null;

        // obj2를 다른 객체로 덮어씌우기
        obj2 = new MyObject("Object 3");

        // 명시적으로 GC 호출
        System.gc();

        // 일부 대기
        try {
            Thread.sleep(1000); // GC가 실행될 시간을 확보
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("End of program");
    }
}

 

실행 결과 (예시):

Garbage collected for object: Object 1
Garbage collected for object: Object 2
End of program

 

설명:

  1. obj1 = null;로 참조를 제거하면 Object 1은 GC 대상이 됩니다.
  2. obj2는 Object 3으로 대체되어 기존의 Object 2도 GC 대상이 됩니다.
  3. System.gc()를 호출하여 GC를 명시적으로 요청합니다. (반드시 실행된다는 보장은 없지만, 요청됩니다.)
  4. finalize() 메서드는 객체가 가비지 컬렉션되기 전에 호출되며, 이를 통해 GC가 작동했음을 확인할 수 있습니다.

가비지 컬렉션 최적화를 위한 팁

  1.  참조 제거
  불필요한 객체에 대한 참조를 명시적으로 제거하여 GC 대상이 될 수 있도록 합니다.

list = null;

 

2.  WeakReference 사용
GC 대상이 되어야 할 객체에 약한 참조를 사용하는 것이 유용할 수 있습니다.

WeakReference<MyObject> weakRef = new WeakReference<>(new MyObject("Weak Object"));

 

3. GC 로깅 활성화
GC 동작을 로깅하여 성능 문제를 파악할 수 있습니다.

-XX:+PrintGCDetails -XX:+PrintGCDateStamps

 

4.  적절한 JVM 옵션 사용
메모리 할당과 GC 설정을 조정하여 애플리케이션 성능을 최적화합니다.

-Xms512m -Xmx1024m -XX:NewRatio=2

 


결론

Java의 메모리 관리와 가비지 컬렉션은 메모리 누수와 같은 문제를 줄이고 안정성을 높이는 중요한 역할을 합니다. 그러나 GC는 성능에 영향을 미칠 수 있으므로, 메모리 관리 최적화와 GC 튜닝을 통해 애플리케이션의 성능을 극대화할 수 있습니다.

가비지 컬렉션을 이해하고 적절히 활용하면 Java 애플리케이션 개발에서 한층 더 높은 수준의 안정성과 효율성을 달성할 수 있습니다.

LIST