자바에서 가비지컬렉터는 객체가 더 이상 참조가 불가능한지 판단하면 객체를 제거하게된다.
죽은 객체의 사회
하지만 가비지컬렉터가 더이상 참조가 불가능하다고 판단한 객체가 반드시 '제거되는 것'은 아니다. 재검과정을 거쳐서 필요한 객체인지 여부를 판단한다.
finalize()
'도달 불가능한 객체'는 재검과정에 들어선다. 재검의 기준은 'finalize() 메서드를 실행해야하는 객체인가'이다.
finalize()는 Object에 정의된 메서드로, finalize()가 필요없거나 이미 호출한 경우 '실행할 필요 없음'으로 처리한다.
반대로 finalize()를 실행해야한다면 F-Queue(F-큐)에 객체를 추가한다.
그러면 나중에 가상머신이 스레드 우선순위가 낮은 종료자 스레드를 생성하여 F-큐에 있는 객체들의 finalize() 메서드를 실행'만'한다. 메서드 실행의 종료까지는 기다려주지 않는다. finalize()가 너무 오래걸리거나 무한루프에 빠지면 기다리다가 가비지컬렉션 시스템 전체가 뻗어버리는 대참사가 발생할 수도 있기 때문이다.
그래서
finalize() 메서드는 죽은 객체가 부활할 수 있는 마지막 기회다.
finalize()를 이용해 부활하려면 참조체인 상의 아무 객체나 다시 연결하면된다.
참조체인과 GC루트에 대해서는 관련 글 작성 중이다. 작성 후 링크를 연결할 예정
class People{
public static People SAVE = null;
...
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalized!");
SAVE = this;
}
}
finalize 코드를 구현해서 finalize() 메서드가 실행되면 People 객체의 인스턴스를 참조하는 정적변수가 자기자신을 참조하도록한다. (static 필드는 GC루트의 대상이된다.)
그래서 finalize()가 실행되면 시작노드에서 참조체인이 연결되며 객체가 부활할 수 있게되는 것이다.
public static void main(String[] args) throws InterruptedException {
People p = new People(28, "만치");
System.out.println(People); //이름: 만치, 나이: 28
p = null;
System.gc(); //finalized!
System.out.println(People.SAVE); //null
Thread.sleep(500);
System.out.println(People.SAVE); //이름: 만치, 나이: 28
}
부활예시
finalize()를 실행하는 종료자 스레드는 우선순위가 낮기 때문에 GC 직후에 People 객체를 호출하면 아직 객체가 부활하지 않아 null이 찍힌다.
finalize() 사용하지 마세요
주의할 점이 몇가지 있는데
finalize() 메서드는 일회성이다. 한 번 finalize() 메서드가 호출되어 부활한 객체는 다시 finalize() 메서드가 호출될 수 없다.
그리고
finalize() 메서드는 객체가 죽어갈 때의 "비극적인 몸부림"이라고 칠 수 있다. 간단한 실습용이나 예시용으로는 구현할 수 있으나 실무용으로는 절대 이런식으로 객체를 구원해서는 안된다.
대표적으로 안되는 이유는, 첫째가 finalize()는 GC 실행 순간에 발생하고, GC 이후 종료자 스레드를 실행시켰을 때야 실행되게되는데, 그 순간을 예측할 수 없다. 둘째, finalize() 내부에서 발생한 에러는 예외처리를 할 수 없다. 그래서 에러가 발생한 순간 메서드가 멈추게되고 메모리 회수가 제대로 이루어지지 않을 수 있는 점 등이 있다.
https://jaeyeong951.medium.com/finalize-%EC%9D%80%ED%87%B4%EC%8B%9D-4a52fb855910
이러한 점 때문에 JDK9부터 finalize()는 Deprecated 되었으며, try-finally, TWR 등의 다른 방식으로 더 잘 관리할 수 있다.
finalize()도 나쁜놈은 아니었다
그럼 왜 finalize() 메서드가 등장했을까?
예전 자바에서는 I/O와 같이 close() 메서드를 반드시 사용해야하는 클래스를 사용한다고 할 때, 개발자가 close()를 까먹는 경우가 있었다. close() 메서드가 실행되지 않은 경우에는 OS 메모리의 누수가 발생할 수 있었고, 그래서 개발자가 close()를 까먹었어도 객체가 소멸되고 GC가 실행되기 전에 close() 메서드를 실행하도록하는 일종의 안전장치의 용도로 등장했다.
그러나 자바에서 AutoCloseable의 구현체에 한해서 자동으로 close() 메서드를 실행시켜주는 TWR이 JDK7에서 등장했고 (JDK9에서 성능개선) 위에서 말한 여러가지 단점들 때문에 finalize() 메서드는 현재 지양되는 편이다.
'언어공부 > Java | Kotlin' 카테고리의 다른 글
| [Java] 자바의 정석 독서 #31 - 어노테이션 (1) | 2025.12.31 |
|---|---|
| [Java] 자바의 정석 독서 #30 - 열거형 (0) | 2025.12.31 |
| [Java] 자바 내에서 참조란 무엇인가? (0) | 2025.12.25 |
| [Java] 자바의 정석 독서 #29 - 제너릭 메서드, 제너릭 형변환 (0) | 2025.12.24 |
| [Java] 자바의 정석 독서 #28 - 제너릭 와일드카드, 공변성 (1) | 2025.12.23 |
