[Java] 리소스 반환
finalizer와 cleaner(자바 9)의 사용 주의
객체 소멸시 자원을 반환할 때 안정성, 성능, 보안, 즉시성 등을 고려할때 그 사용을 최소한으로 하고 안전망 역할이나 GC의 대상이 되지 않는 네이티브 피어의 자원 회수용으로만 사용해야 한다.
try-with-resources
AutoCloseable
의 close
메소드를 구현하고 try-with-resources
구문을 이용하여 자동적으로 리소스가 반환되도록 하고 client에서의 실수를 방지하기 위한 안전망으로 cleaner
를 이용한다.
사용 예제
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private static class State implements Runnable {
int numJunkPiles;
public State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
@Override
public void run() {
System.out.println("방 청소");
numJunkPiles = 0;
}
}
private final State state;
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
this.state = new State(numJunkPiles);
this.cleanable = cleaner.register(this, state);
}
@Override
public void close() throws Exception {
cleanable.clean();
}
}
- 자동적으로
close
메소드가 실행되지 않는다면 GC가Room
객체를 회수할 때State
의run
메소드를 실행줄 것을 기대할 수 있다. - 내부 클래스를 정적으로 만들지 않으면 마치 인스턴스 메소드와 같이 외부 클래스에 대한 참조를 가지게 되어 순환 참조가 생기게 된다. 이는 GC 대상에서 제외되므로
static
키워드를 붙여야 한다. - 람다식의 경우 익명 클래스의 인스턴스이므로 람다식 내에서 외부 클래스의 변수에 접근한다면 외부 클래스에 대한 참조를 가지게 되어 순환 참조가 생기게 된다. 이는 GC 대상에서 제외되므로
static
키워드를 붙여야 한다. java.lang.AutoCloseable
또는 이것을 상속한java.io.Closeable
이 존재하는데Closeable
의 경우 입출력과 관련된IOException
예외를 던지게 되어있다. 따라서 입출력과 관련된 자원의 반납의 경우Closeable
인터페이스를 구현하는 것이 더 적적할 수 있다.
Finalizer 공격
public class Account {
private String accountId;
public Account(String accountId) {
this.accountId = accountId;
if (accountId.equals("푸틴")) {
throw new IllegalArgumentException("푸틴은 계정을 막습니다.");
}
}
public void transfer(BigDecimal amount, String to) {
System.out.printf("transfer %f from %s to %s\n", amount, accountId, to);
}
}
public class BrokenAccount extends Account {
public BrokenAccount(String accountId) {
super(accountId);
}
@Override
protected void finalize() throws Throwable {
this.transfer(BigDecimal.valueOf(100), "keesun");
}
}
- 위와 같은 방법으로
푸틴
이라는accountId
를 갖는BrokenAccount
인스턴스 생성이 실패하기는 하지만finalize
의 실행으로transfer
메서드의 실행이 가능해진다. - 위와 같은 공격을 방어하기 위해서는
final
키워드를 통해 상속을 금지하거나finalize
의 오버라이딩을 금지시켜야 한다.
댓글남기기