자바의 소멸자
카테고리: Java
태그: Java
finalize
자바의 모든 클래스는 최상위 클래스인 Object 클래스의 여러 메서드를 포함하고 있습니다. 객체 소멸자라고 말하는 finalize() 메서드도 그 메서드들 중 하나입니다. 리소스 누수(leak)
를 방지하기 위해 자바 JVM(Java Virtual Machine)
이 실행하는 가비지 컬렉션
이 수행될 때 finalize() 사용합니다. JVM이 object.finalize() 메서드를 실행하고 애플리케이션별 코드가 I/O 스트림이나 데이터스토어에 대한 핸들과 같은 리소스를 클린업
한다는 개념입니다.
public class Finalizable {
private BufferedReader reader;
public Finalizable() {
InputStream input = this.getClass()
.getClassLoader()
.getResourceAsStream("file.txt");
this.reader = new BufferedReader(new InputStreamReader(input));
}
public String readFirstLine() throws IOException {
String firstLine = reader.readLine();
return firstLine;
}
// other class members
}
위의 클래스는 반환가능한 자원
을 참조하는 reader 필드를 가지고 있습니다.
주의해야할 점은 위의 코드에서 reader
가 close
되는 코드가 없다는 것입니다.
아래의 코드는 소멸자를 통해 reader에서 사용하는 System resource
을 반환할 수 있습니다.
@Override
public void finalize() {
try {
reader.close();
System.out.println("Closed BufferedReader in the finalizer");
} catch (IOException e) {
// ...
}
}
자바9 부터 finalize() 메서드는 Deprecated 되었습니다.
finalize의 문제점
finalize() 메서드는 가비지 컬렉션
이 수행될 때, JVM이 호출하기 때문에 호출 시점을 예측할 수 없고, 예상과는 다르게 동작할 수 있고, 올바르게 구현하기 어렵다는 단점이 있습니다.
finalize() 메서드의 호출 시점을 예상할 수 없다
finalize() 메서드의 호출 시점을 예상할 수 없다는 것 자체는 문제가 되지 않습니다. finalize() 메서드는 늦든 빠르든 결국에는 호출되기 때문입니다. 하지만 System resource
는 한정되어 있습니다. 따라서 자원의 클린업
이 수행되기 전에 System resource
가 부족하여 시스템 충돌이 발생할 수 있습니다.
예측불가능한 코드
class FinalizeThis {
protected void finalize() {
System.out.println("finalized!");
}
void loop() {
System.out.println("loop() called");
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
System.out.println("loop() returns");
}
public static void main(String[] args) {
// FinalizeThie의 인스턴스를 참조하는 객체가 없으므로, GC의 대상이 됩니다.
new FinalizeThis().loop();
}
}
위의 코드에서 FinalizeThis().loop() 메서드가 실행 중
이도, 인스턴스를 참조하는 대상이 없기 때문에, GC는 이 인스턴스를 unreachable
하다고 판단합니다. 따라서 GC의 대상이 되고 개발자의 의도와 다르게 어플리케이션이 동작할 수 있습니다.
잘못된 finalize 구현
public class Zombie {
static Zombie zombie;
public void finalize() {
zombie = this;
}
}
위의 코드는 GC의 대상이었던 객체가 reachable
상태가 되기 때문에 GC의 대상에서 제외되게 됩니다.
Cleaner
finalize의 문제점을 지적하며 Cleaner 클래스가 자바 9에 도입됐습니다. Cleaner 클래스는 클린업이 필요한 객체를 사용하는 코드
와 클린업 루틴
을 분리한다는 개념으로 다음 세 가지로 분리하였습니다
- 핸들 객체: 원래 프로그래밍 인터페이스를 유지하는 핸들 객체
- 내부 상태 객체: 네이티브 리소스를 식별하고 최종적으로 리소스를 할당 해제하기 위한 모든 정보를 포함하는 내부 상태 객체
- 내부 상태 객체는 외부에서 참조되지 않으며, 모든 액세스는 핸들 객체에 의해 매개됩니다.
- 클리너 인프라: 모든 인스턴스가 공유하는 글로벌 클리너 객체와 닫기를 통해 명시적 할당 해제를 구현하는 데 사용되는 각 핸들에 특정한 클리너 객체입니다.
Cleaner 인스터스의 clean 메서드가 실행될 때, 상태 객체
가 해제해야 하는 자원을 알려주며, 동시에 클리너 인프라
가 제거해야할 객체와 관련된 모든 자원 회수합니다.
Cleaner 클래스도 문제가 있다.
Cleaner 클래스는 finalize의 일부 문제를 해결하긴 했지만, 수행 시점뿐 아니라 수행 여부조차 보장하지 못한다는 문제가 남아있습니다.
AutoCloseable
위에서 언급한 문제들 때문에, finalize와 Cleaner을 사용하는 것은 좋은 선택이 아닐 수 있습니다.
소멸자를 호출하지 않고 System resource
를 반환하는 방법은 여러가지가 있습니다. Autoclosable을 구현하고 사용하는 것도 방법 중 하나입니다.
public class CloseableResource implements AutoCloseable {
private BufferedReader reader;
public CloseableResource() {
InputStream input = this.getClass()
.getClassLoader()
.getResourceAsStream("file.txt");
reader = new BufferedReader(new InputStreamReader(input));
}
public String readFirstLine() throws IOException {
String firstLine = reader.readLine();
return firstLine;
}
@Override
public void close() {
try {
reader.close();
System.out.println("Closed BufferedReader in the close method");
} catch (IOException e) {
// handle exception
}
}
}
CloseableResource 클래스와 본 글의 처음에 나온 Finalizable 클래스와의 차이점은 AutoCloseable
의 인터페이스 close()
를 구현했는 점입니다. 여기서 주목할만한 점은 Finalizable 클래스의 finalize() 메서드와 CloseableResource 클래스의 close()의 코드는 거의 똑같다는 점입니다.
@Test
public void whenTryWResourcesExits_thenResourceClosed() throws IOException {
try (CloseableResource resource = new CloseableResource()) {
String firstLine = resource.readFirstLine();
assertEquals("cire0304.github.io", firstLine);
}
}
위의 코드는 try-with-resources 블록안에서 파일을 읽는 등의 작업을 하기 때문에, 자동적으로 작업이 끝난 후 System resourc
를 반환하고 있습니다.
자바의 소멸자는 겉으로 보기에 유용해보이지만 심각한 사이드 이펙트를 유발할 수 있고, 소멸자를 사용하지 않는 다양한 대안들이 있기 때문에, 소멸자의 사용의 지양
하는 것이 좋습니다.
출처
https://www.baeldung.com/java-finalize
https://www.itworld.co.kr/news/224419
https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-objects-in-java-8/26645534#26645534
댓글 남기기