Object 클래스에 정의된 메서드로, GC(Garbage Collector)가 객체를 메모리에서 제거하기 직전에 호출되도록 설계된 메서드다.
// Object 내부 선언
protected void finalize() throws Throwable { }
즉, 개발자가 이 메서드를 오버라이딩해두면 "객체가 사라지기 전에 이 코드를 실행해줘" 라는 의도로 만들어진 것이다.
1. 객체가 더 이상 참조되지 않음 (참조값이 없어짐)
↓
2. GC 대상으로 분류됨
↓
3. GC가 finalize() 호출
↓
4. finalize() 실행 완료 후 메모리에서 제거
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 종료
| 버전 | 내용 |
|---|---|
| Java 9 | @Deprecated 지정 |
| Java 18 | @Deprecated(forRemoval = true) 로 제거 예정 명시 |
| Java 21 | 실질적으로 사용 불가 수준으로 기능 약화 |
GC는 JVM이 필요하다고 판단할 때 실행된다.
finalize()가 언제 호출될지, 심지어 호출되기는 할지조차 보장되지 않는다.
e = null;
System.gc(); // 요청이지 강제가 아님
// finalize()가 호출 안 될 수도 있음
finalize()를 오버라이딩한 객체는 GC가 두 번에 걸쳐 처리한다.
일반 객체: GC 1번 → 즉시 제거
finalize() 오버라이딩 객체:
1차 GC → finalize() 실행 큐에 등록
2차 GC → 그제서야 제거
GC 사이클이 2배로 늘어나므로 메모리 해제가 지연된다.
finalize() 안에서 this를 다른 참조변수에 다시 담으면 객체가 GC에서 살아남는다.
@Override
protected void finalize() throws Throwable {
EmployeeManager.saveEmp = this; // this를 다시 참조 → 객체 부활
}
이렇게 부활한 객체는 두 번째 GC 때 finalize()가 다시 호출되지 않는다.
예측 불가능한 객체 생명주기를 만들어낸다.
finalize() 내부에서 예외가 발생해도 JVM이 예외를 그냥 무시하고 종료한다.
@Override
protected void finalize() throws Throwable {
throw new RuntimeException("에러 발생");
// 이 예외는 출력도 안 되고 전파도 안 됨 → 완전히 무시됨
}
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 블록 종료 시점에 반드시 호출된다.
finalize()는 "객체 소멸 전 자원 정리"라는 좋은 의도로 설계됐지만,
호출 시점 미보장 / 성능 저하 / 객체 부활 / 예외 무시라는 치명적인 문제들로 인해 결국 Java에서 제거 수순을 밟고 있다.