본문 바로가기
Java

Effective Java #07 종료자 사용을 피하라

by NaHyungMin 2016. 4. 13.

종료자(finalizer)는 예측 불가능하며, 대체로 위험하고 일반적으로 불필요하다.


c++에서 소멸자는 메모리 이외의 자원을 반환하는 데도 사용되는데, 자바에서는 보통 try-finally 블록이 그런 용도로 사용된다.

종료자의 한가지 단점은 즉시 실행되리라는 보장이 전혀 없다는 것이다.

따라서 긴급한(time-critical) 작업을 종료자 안에서 처리하면 안된다.

예를 들면 종료자 안에서 파일을 닫도록 하면 치명적이다.


그 외, 주요정보, 동기화나 입출력 및 자원을 점유하고 반환해야 하는 곳에서 종료자를 사용하여 처리하면 메모리 타이밍이 맞질 않을 것 같다.

System.gc나 System.runFinalization 같은 함수에 마음이 흔들리면 곤란하다.

이런 함수들은 종료자가 실행될 가능성을 높여주긴 하는데 보장하진 않는다.

종료자를 보장하는 함수는 System.runFinalizersOnExit와 Runtime.runFinalizersOnExit 뿐인데 이 함수는 심각한 결함을 갖고 있어 폐기 되었다.


종료자를 사용하면, 프로그램 성능도 떨어진다 한다.

예를 들면 객체 삭제 시, 5.6ns 걸리는 것이 종료자를 붙이자 시간이 2,400ns로 늘어난다 한다.


파일이나 스레드처럼 명시적 반환하거나 삭제해야 하는 자원을 포함하는 객체의 클래스는 어떻게 작성해야 하는 걸까?

답은 명시적인 종료 함수를 하나 구현하고 더 이상 필요하지 않은 객체를 해당 함수에서 호출하게 해야한다.

이런 명시적 종료 함수를 예로는 OutputStream이나 InputStream, Java.sql.Connection에 정의된 close 함수가 있다.


책의 저자에 의하면 객체의 반환이 성능에 영향을 많이 미친다고 한다.

위에 종료 함수는 보통 try-finally 문과 함께 쓰인다. 객체 종료를 보장하기 위해서다.


1
2
3
4
5
6
7
8
9
//try-finally 블록을 통해 종료 함수 실행 보장
Foo foo = new Foo(...);
try {
    //실행 작업
    ...
}finally{
    //종료 함수 호출
    foo.terminate();
}
cs


그럼 종료자는 정말로 써먹을 데가 없는 건 아니다.

적합한 곳이 두 군데 정도 있다.

1. 명시적 종료 함수 호출을 잊을 경우에 대비하는 안전망(종료자가 언제 호출될 지 알 수 없지만, 어쨋든 자원은 반환된다.)

2. 네이티브 피어(native peer)와 연결결된 객체를 다룰 때다.(네이티브 피어는 일반 자바 객체가 네이티브 함수를 통해 기능 수행을 위임하는 네이티브 객체를 말한다.)


만약 종료자를 써야 한다면. 자세하게 맞는 코드를 찾아 쓰는걸 추천한다.


자바7이상부터 try-with-resources를 지원한다고 한다.

사용법은 다음과 같다.


1
2
3
4
5
try(SampleClass sampleClass = new SampleClass){
    //작업
}catch(...) {
    ...
}
cs



C#에서 사용법은 다음과 같다.


1
2
3
4
5
using(SampleClass sampleClass = new SampleClass)
{
    //작업
    ...
}
cs


예는 일반 클래스 객체로 들었지만 FileStream 같은 경우는 Close같은 종료를 자동으로 해준다 한다.