카테고리 없음

가비지 컬렉션(GC)이란..?

hyeijoo1234 2025. 4. 21. 09:44

금요일의  학습주제인 가비지 컬렉션(Garbage Collection)에 대해서 학습해보았다. 그냥 JVM이 알아서 메모리 관리해준다~ 이 정도로만 알고 있었는데, 정확히 언제 객체가 제거되는지, 어떻게 동작하는지 궁금해서 좀 더 찾아봤다.

추가적으로 가비지 컬렉션이랑 가비지 컬렉터..? 차이점이 무엇일까 먼저 알아보았다.

 


가비지 컬렉션(Garbage Collection) vs 가비지 컬렉터(Garbage Collector)

이 두 용어는 비슷하게 들리지만, 의미가 다름.

용어 의미

가비지 컬렉션 (Garbage Collection) 더 이상 사용하지 않는 객체를 메모리에서 제거하는 행위 또는 과정 자체
가비지 컬렉터 (Garbage Collector) 그 행위를 실제로 수행하는 JVM 내부의 기능 또는 모듈(=청소부 역할)

즉, 비유하자면:

  • 가비지 컬렉션은 "청소하는 작업"
  • 가비지 컬렉터는 "청소하는 사람"이라고 생각하면 됨

자바에서는 GC(Garbage Collection)를 자동으로 해주는데, 이걸 실제로 실행하는 주체가 바로 Garbage Collector다.
JVM마다 다른 GC 알고리즘(G1, ZGC, Serial GC, Parallel GC 등)을 선택할 수 있고, 이건 어떤 가비지 컬렉터를 쓰느냐에 따라 달라진다.


 

가비지 컬렉션이란?

가비지 컬렉션은 자바에서 더 이상 사용하지 않는 객체를 자동으로 메모리에서 정리해주는 기능이다. 자바에서는 객체를 new로 만들면 힙(heap) 메모리에 올라가는데, 이걸 우리가 직접 지우지 않아도 JVM이 주기적으로 확인해서 제거해준다.

C 언어나 C++처럼 수동으로 메모리를 해제하지 않아도 되는 게 큰 장점인데, 이 덕분에 **메모리 누수(memory leak)**를 어느 정도 방지할 수 있고, 개발자가 로직에만 집중할 수 있다.

 

 

 

그럼 언제 객체가 GC 대상이 될까?

객체가 어떤 변수에서도 더 이상 참조되지 않을 때 GC 대상이 된다. 예를 들어 아래처럼:

public class GarbageTest {
    public static void main(String[] args) {
        String a = new String("hello");
        String b = new String("world");

        a = b;  // 이제 "hello"는 아무도 안 쓰는 상태 → GC 대상
    }
}

위 코드에서 a = b가 실행되면 "hello" 객체는 아무도 참조하지 않게 되니까, 이 시점부터 GC 대상이 된다.

 

 

 

예제 코드로 확인해보기

public class MyObject {
    int[] bigArray = new int[1000000];  // 메모리 많이 차지

    @Override
    protected void finalize() throws Throwable {
        System.out.println("객체가 가비지 컬렉션됨");
    }
}

public class Main {
    public static void main(String[] args) {
        MyObject obj = new MyObject();
        obj = null;  // 참조 끊김

        System.gc();  // GC 요청 (실제로 실행된다는 보장은 없음)

        try {
            Thread.sleep(1000);  // finalize 출력 기다리기
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

System.gc()는 JVM에게 가비지 컬렉션을 "요청"하는 거지 강제로 실행시키는 게 아니고, 실제로 언제 실행될지는 JVM이 판단한다.

 

 

 

왜 가비지컬렉션이 필요한가?

프로그래밍할 때 메모리는 한정돼 있기 때문에, 더 이상 쓰지 않는 객체를 계속 쌓아두면 메모리 부족(OOM: Out Of Memory) 오류가 날 수밖에 없다.
C 같은 언어에서는 이런 걸 개발자가 수동으로 관리해야 해서, 실수하면 메모리 누수가 생기거나, 이미 해제된 메모리를 참조하는 버그가 발생할 수 있다.

자바는 GC 덕분에 이런 실수를 줄일 수 있고, 시스템 자원을 효율적으로 관리할 수 있다.
다만 GC가 돌아가는 타이밍은 개발자가 제어할 수 없고, 실행 중에 잠깐 프로그램을 멈추는 경우도 있어서, 퍼포먼스가 중요한 서비스에서는 GC 튜닝이 꽤 중요하다고 한다.

 

 

 

GC는 어떻게 작동할까?

자바에서 기본적으로 사용하는 방식은 Mark and Sweep (마크 앤 스윕) 방식이다.

  1. Mark 단계에서는 루트에서 참조 가능한 객체들을 따라가면서 표시하고
  2. Sweep 단계에서는 표시되지 않은 객체들을 메모리에서 제거한다

이 과정을 통해 불필요한 객체들을 자동으로 정리하게 된다.

 

 

쉽게 말하자면 시험결과정리 단계에서

Mark단계는 시험에서 합격자들을 따로 표시해두고

Sweep단계는 합격자들을 추출하고 불합격자들의 명단을 제거해버리는 단계인것이다.

 

 

 

힙 영역은 어떻게 나뉘어 있을까?

이 부분은 처음에 몰랐는데, GC가 메모리를 효율적으로 관리하기 위해 세대(Generation) 단위로 힙 영역을 나눠서 관리한다고 한다. 찾아보니까 자바는 아래처럼 나뉘어 있음:

1. Young Generation (영 제너레이션)

  • 새로 생성된 객체들이 들어가는 공간
  • 대부분 금방 사라지기 때문에 GC가 자주 일어남
  • 여기서 일어나는 GC는 Minor GC

2. Old Generation (올드 제너레이션)

  • Young에서 살아남은 객체들이 이동됨
  • 오래 살아남는 객체들이 많기 때문에 GC가 느리고 무겁다
  • 여기서 일어나는 GC는 Major GC 또는 Full GC

3. Metaspace (메타스페이스)

  • 클래스 정보 같은 메타데이터가 저장되는 공간
  • 예전에는 PermGen이었는데, 자바 8부터는 Metaspace로 바뀌어서 힙이 아닌 네이티브 메모리를 사용함

 

 

 

Minor GC vs Major GC 정리

항목 Minor GC Major (Full) GC

대상 Young Generation Old + Young
속도 빠름 느림
발생 빈도 자주 드물게
프로그램 정지 짧음 길 수 있음 (Stop-the-world 현상)

그래서 실제로 어플리케이션 성능 튜닝할 때는, Minor GC는 좀 자주 일어나도 괜찮은데, Major GC가 자주 발생하면 앱이 멈추는 시간이 커져서 문제된다고 한다.

 

 

<결론>

GC 덕분에 자바는 메모리 관리를 자동으로 해줘서 개발자가 따로 신경 쓸 일이 줄어든다.
하지만 실제 서비스 환경에서는 GC의 동작 방식, 어떤 알고리즘을 쓰는지, 어떤 GC가 더 적절한지 등을 알아두는 게 꽤 중요할 것 같다.

GC는 편한 기능이지만, 그만큼 성능에 미치는 영향도 있어서 앞으로 JVM 튜닝 같은 것도 조금씩 공부해봐야겠다.