[TIL]finalize()

revo·2026년 4월 9일

자바

목록 보기
20/30
post-thumbnail

1. finalize()란?

Object 클래스에 정의된 메서드로, GC(Garbage Collector)가 객체를 메모리에서 제거하기 직전에 호출되도록 설계된 메서드다.

// Object 내부 선언
protected void finalize() throws Throwable { }

즉, 개발자가 이 메서드를 오버라이딩해두면 "객체가 사라지기 전에 이 코드를 실행해줘" 라는 의도로 만들어진 것이다.


2. 동작 원리

1. 객체가 더 이상 참조되지 않음 (참조값이 없어짐)
        ↓
2. GC 대상으로 분류됨
        ↓
3. GC가 finalize() 호출
        ↓
4. finalize() 실행 완료 후 메모리에서 제거

3. 예제

package chapter06;

public class Engineer extends Employee {
    private String skill;

    public Engineer(String empno, String name, int salary, String skill) {
        super(empno, name, salary);
        this.skill = skill;
    }

    // GC가 이 객체를 수거하기 직전에 호출됨
    @Override
    protected void finalize() throws Throwable {
        System.out.println(getName() + " 객체가 GC에 의해 제거됩니다.");
        super.finalize();  // 부모(Object)의 finalize() 호출
    }

    @Override
    public String toString() {
        return super.toString() + ", skill=" + skill;
    }

    public String getSkill()           { return skill; }
    public void setSkill(String skill) { this.skill = skill; }
}
package chapter06;

public class MainEmp {
    public static void main(String[] args) throws InterruptedException {

        Engineer e = new Engineer("D002", "그레이몬", 3500000, "불꽃폭발");
        System.out.println("객체 생성: " + e.getName());

        e = null;  // 참조 제거 → GC 대상이 됨

        System.gc();           // GC 실행 요청 (보장되지 않음)
        Thread.sleep(1000);    // GC가 실행될 시간을 줌

        System.out.println("main 종료");
    }
}
객체 생성: 그레이몬
그레이몬 객체가 GC에 의해 제거됩니다.
main 종료

4. Deprecated → 제거까지의 역사

버전내용
Java 9@Deprecated 지정
Java 18@Deprecated(forRemoval = true)제거 예정 명시
Java 21실질적으로 사용 불가 수준으로 기능 약화

5. 위험성

위험 1 - 호출 시점을 보장할 수 없다

GC는 JVM이 필요하다고 판단할 때 실행된다.
finalize()언제 호출될지, 심지어 호출되기는 할지조차 보장되지 않는다.

e = null;
System.gc();  // 요청이지 강제가 아님
// finalize()가 호출 안 될 수도 있음

위험 2 - 성능 저하

finalize()를 오버라이딩한 객체는 GC가 두 번에 걸쳐 처리한다.

일반 객체: GC 1번 → 즉시 제거
finalize() 오버라이딩 객체:
    1차 GC → finalize() 실행 큐에 등록
    2차 GC → 그제서야 제거

GC 사이클이 2배로 늘어나므로 메모리 해제가 지연된다.

위험 3 - 부활(Resurrection) 가능성

finalize() 안에서 this를 다른 참조변수에 다시 담으면 객체가 GC에서 살아남는다.

@Override
protected void finalize() throws Throwable {
    EmployeeManager.saveEmp = this;  // this를 다시 참조 → 객체 부활
}

이렇게 부활한 객체는 두 번째 GC 때 finalize()가 다시 호출되지 않는다.
예측 불가능한 객체 생명주기를 만들어낸다.

위험 4 - 예외가 무시된다

finalize() 내부에서 예외가 발생해도 JVM이 예외를 그냥 무시하고 종료한다.

@Override
protected void finalize() throws Throwable {
    throw new RuntimeException("에러 발생");
    // 이 예외는 출력도 안 되고 전파도 안 됨 → 완전히 무시됨
}

6. 대안

finalize() 대신 Java 7부터 도입된 AutoCloseable + try-with-resources를 사용해야 한다.

public class Engineer extends Employee implements AutoCloseable {

    public Engineer(String empno, String name, int salary, String skill) {
        super(empno, name, salary);
        this.skill = skill;
    }

    @Override
    public void close() {
        // 자원 해제 로직을 여기에
        System.out.println(getName() + " 자원 해제 완료");
    }
}
// try 블록이 끝나면 close()가 자동 호출됨 → 시점 보장
try (Engineer e = new Engineer("D002", "그레이몬", 3500000, "불꽃폭발")) {
    System.out.println(e.getName() + " 사용 중");
}  // ← 여기서 close() 자동 호출
그레이몬 사용 중
그레이몬 자원 해제 완료

finalize()와 달리 close()는 try 블록 종료 시점에 반드시 호출된다.


7. 정리

finalize()는 "객체 소멸 전 자원 정리"라는 좋은 의도로 설계됐지만,
호출 시점 미보장 / 성능 저하 / 객체 부활 / 예외 무시라는 치명적인 문제들로 인해 결국 Java에서 제거 수순을 밟고 있다.

0개의 댓글