[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의 오버라이딩을 금지시켜야 한다.
 
      
    
댓글남기기