일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- kafka
- 스프링 프레임워크
- 스프링 컨테이너
- resultMap
- java
- spring
- db
- SQL
- mybatis
- 스프링
- JPA
- DIP
- assertThat
- 생성자 주입
- 스프링 부트 기본
- Effective Java
- 싱글톤
- 스프링부트
- springboot
- 스프링 빈
- 필드 주입
- 스프링 부트
- sqld
- @Configuration
- DI
- thymeleaf
- assertThrows
- jdbc
- 스프링 부트 입문
- Javascript
- Today
- Total
선 조치 후 분석
[Effective Java] 아이템8 - finalizer와 cleaner 사용을 피하라 본문
[Effective Java] 아이템8 - finalizer와 cleaner 사용을 피하라
JB1104 2023. 11. 27. 15:02자바에서 객체소멸은 GC (Gabage Collector)가 담당하고,
비 메모리 자원회수는 try-with-resources, try-finally로 해결한다.
자바에서 제공하는 2가지 객체 소멸자, finalizer와 cleaner는 기본적으로 사용하지 말아야 한다.
사용하면 안 되는 이유
- finalizer : 예측할 수 없고, 위험하며, 느리고, 일반적으로 불필요하다.
- cleaner : finalizer보다는 덜 위험하지만 여전히 예측할 수 없고, 느리며, 보통은 불필요하다.
단점 1 - 즉시 수행된다는 보장이 없다
finalizer와 cleaner는 호출된 후 언제 실행될지 알 수 없다. 즉, 제때 실행되어야 하는 작업을 절대 할 수 없다.
finalizer와 cleaner의 수행 속도는 가비지 컬렉터에 달렸으며, 가비지 컬렉터 구현마다 다르다.
가비지 컬렉터가 finalize()를 가비지 컬렉터의 대상으로 한다. 문제는, 이 메서드가 언제 호출될지 모른다는 것이다.
단점 2 - 자원 횟수가 제멋대로 지연된다
인스턴스 반남을 지연시킬 수 있으며, 심지어 실행이 되지 않을 수 있다.
finalizer 스레드는 다른 애플리케이션 스레드보다 우선순위가 낮아 실행될 기회를 제대로 얻지 못할 수 있다.
cleaner는 자신을 수행할 스레드를 제어할 수는 있지만, 역시나 가비지 컬렉터에 의존하므로 사용하지 않는다.
단점 3 - 수행 여부조차 보장하지 않는다
상태를 영구적으로 수정하는 작업에서는 절대 finalizer나 cleaner에 의존하면 안 된다.
public class FinalizerDemo {
@Override
protected void finalize() throws Throwable {
System.out.println("Clean Up");
}
public void hello() {
System.out.println("hello");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
new Main().run();
Thread.sleep(1000);
}
private void run() {
FinalizerDemo finalizerDemo = new FinalizerDemo();
finalizerDemo.hello();
}
}
//출력결과 :hello
위 코드에서 run() 메서드가 종료되면 더 이상 FinalizerDemo 인스턴스에 대한 참조가 존재하지 않아서
finalize 메서드가 실행되면서 "Clean Up"이 출력될 것을 기대할 수 있지만, 기대와는 다르게 finalize() 메서드는 호출되지 않는다.
접근할 수 없는 객체에 대한 종료 작업을 전혀 수행하지 못한 채 프로그램이 중단될 수 있다.
System.gc나 System.runFinalization 메서드는 실행 가능성을 높여줄 순 있으나, 보장하진 않는다.
System.runFinalizersOnExit와 Runtime.runFinalizersOnExit는 보장해 주긴 하나,
다른 스레드가 소멸 대상의 객체에 접근하고 있어도 실행해 버린다는 매우 치명적인 결함이 있다.
단점 4 - 동작 중 발생한 예외가 무시된다
finalizer는 동작 중 발생할 예외를 무시하며, 처리할 작업이 남아있더라도 그 순간 종료된다.
잡지 못한 예외 때문에 해당 객체는 훼손될 수 있고, 다른 스레드가 이 훼손된 객체에 접근하게 될 수 있다.
Cleaner는 자신의 스레드를 통제하기 때문에 위의 문제는 발생하지 않는다.
단점 5 - 가비지 컬렉터의 효율을 떨어뜨린다
finalizer와 cleaner는 가비지 컬렉터의 효율을 떨어트리기 때문에 심각한 성능문제도 있다.
단점 6 - 보안 문제를 일으킬 수 있다
생성자나 직렬화 과정에서 예외가 발생하면 finalizer가 수행되는데, 이 finalizer를 오버라이딩한 하위 클래스의
finalizer가 수행될 수 있다. 심지어, 이 finalizer를 static 필드에 할당하면 가비지 컬렉터에 의해 수거되지도 않는다.
인스턴스를 역 직렬화 시, readResolve 메서드가 실행되고, 이 메서드는 새로운 인스턴스를 반환한다.
A라는 클래스가 있다고 가정하자. A를 B라는 클래스가 상속을 받았다. finalizer를 오버라이딩 한 B는 생성자에서
예외를 던진다. 인스턴스를 생성할 때 예외가 던져지면, 객체는 생성되지 않고 수거되어야 하는데,
객체가 죽으면서 finalize가 실행된다. finalize 메서드가 안에서 이 인스턴스가 가진 static 필드에 접근할 수 있어서
인스턴스 자체가 GC가 되지 못하게 할 수 있다. 제거되어야 하는 인스턴스가 finalize 때문에 제거되지 않는다.
해결 - 상속 자체를 막을 수 있는 final 클래스로 만들거나, A 클래스 안에서 아무 일도 하지 않는 final로 선언된 finalize 메서드를 만든다.
정상적으로 자원 반납하는 방법 - AutoCloseable
파일이나 스레드 등 종료해야 할 자원을 담고 있는 객체의 클래스에서 AutoCloseable을 구현해 주고, 클라이언트에서
인스턴스를 다 쓰고 나면 close 메서드를 호출하면 된다.
일반적으로 예외가 발생하면 제대로 종료되도록 try-with-resource를 사용해야 한다.
각 인스턴스는 자신이 닫혔는지를 추적하기 위해, close 메서드 호출 여부를 필드로 저장한다.
close 메서드에서 이 객체는 더 이상 유효하지 않음을 필드에 기록하고, 다른 메서드는 이 필드를 검사해서 객체가 닫힌 후에 불렸다면 IllegalStateException을 던지도록 구현한다.
try-finally : 명시적으로 자원 반납
public class SampleResource implements AutoCloseable {
@Override
public void close() throws RuntimeException {
System.out.println("Close");
}
public void hello() {
System.out.println("hello");
}
}
public class Main2 {
public static void main(String[] args) {
SampleResource resource = null;
try {
resource = new SampleResource();
resource.hello(); // 리소스 사용
} finally {
resource.close(); // 리소스를 사용하는 쪽에서 반드시 정리
}
}
}
resource를 사용한 쪽에서 사용한 다음 반드시 close를 호출하여 리소스를 정리해줘야 한다.
이를 보장하기 위해서 try - finally block을 사용한다. (무조건 close가 호출되도록)
hello
Close
try-with-resource : 암묵적으로 자원 반납, 가장 이상적인 자원 반납 방법
AutoCloseable을 구현하면, 명시적으로 close를 호출하지 않아도 try 블록이 끝날 때, close를 호출한다.
public class SampleResource2 implements AutoCloseable {
@Override
public void close() throws RuntimeException {
System.out.println("Close");
}
public void hello() {
System.out.println("hello");
}
}
public class Main2 {
public static void main(String[] args) {
// try-with-resource : 암묵적으로 자원 반납
try(SampleResource2 resource2 = new SampleResource2()) {
resource2.hello(); // 리소스 사용
}
}
}
hello
Close
finalizer와 cleaner는 언제 사용할까?
1. AutoCloseable을 구현하지 않았을 경우를 대비한 '안전망' 역할을 한다.
클라이언트가 놓친 자원 회수를 늦게라도 해주는 것이 아예 안 하는 것보다 낫다.
finalizer
public class SampleResource3 implements AutoCloseable {
private boolean closed;
@Override
public void close() throws RuntimeException {
if(this.closed) {
throw new IllegalStateException();
}
closed = true;
System.out.println("Close");
}
public void hello() {
System.out.println("hello");
}
@Override
protected void finalize() throws Throwable {
// 클라이언트 쪽에서 close를 놓칠 수 있어서, 안전망 삼아서 한번 더 호출
if(!this.closed) close();
}
}
cleaner
public class SampleResource4 implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// 청소가 필요한 자원으로 절대 Room을 참조해서는 안된다.
private static class State implements Runnable {
int numJunkFiles; // 방 안의 쓰레기 수
public State(int numJunkFiles) {
this.numJunkFiles = numJunkFiles;
}
// close 메서드나 cleaner가 호출한다
@Override
public void run() {
System.out.println("방 청소");
numJunkFiles = 0;
}
}
// 방의 상태. cleanable와 공유한다.
private final State state;
// cleanable 객체. 수거 대상이 되면 방을 청소한다.
private final Cleaner.Cleanable cleanable;
public SampleResource4(State state, Cleaner.Cleanable cleanable) {
this.state = new State(state.numJunkFiles);
this.cleanable = cleaner.register(this, state);
}
@Override
public void close() throws Exception {
cleanable.clean();
}
}
2. 네이티브 피어와 연결된 객체
네이티브 피어
일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체.
자가 객체가 아니라서 가비 컬렉터의 대상이 되지 못한다. 즉시 회수해야 한다면 close를 사용한다.
'Language > Effective Java' 카테고리의 다른 글
[Effective Java] 아이템10 - equals는 일반 규악을 지켜 재정의하라 (0) | 2024.02.19 |
---|---|
[Effective Java] 아이템9 - try-finally 대신 try-with-resources를 사용하라 (3) | 2023.11.28 |
[Effective Java] 아이템7 - 다 쓴 객체 참조를 해제하라 (1) | 2023.11.27 |
[Effective Java] 아이템6 - 불피요한 객체 생성을 피하라 (1) | 2023.11.23 |
[Effective Java] 아이템4 - 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2023.11.23 |