[Java] 리소스 반환

1 분 소요

finalizer와 cleaner(자바 9)의 사용 주의

객체 소멸시 자원을 반환할 때 안정성, 성능, 보안, 즉시성 등을 고려할때 그 사용을 최소한으로 하고 안전망 역할이나 GC의 대상이 되지 않는 네이티브 피어의 자원 회수용으로만 사용해야 한다.

try-with-resources

AutoCloseableclose 메소드를 구현하고 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 객체를 회수할 때 Staterun 메소드를 실행줄 것을 기대할 수 있다.
  • 내부 클래스를 정적으로 만들지 않으면 마치 인스턴스 메소드와 같이 외부 클래스에 대한 참조를 가지게 되어 순환 참조가 생기게 된다. 이는 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의 오버라이딩을 금지시켜야 한다.

댓글남기기