본문 바로가기
Language/Effective java

객체 생성과 파괴

by y.j 2022. 6. 5.
728x90

finalizer와 cleaner 사용을 피하라.

finalizer는 예측 할 수 없고, 상황에 따라 위험 할 수 있어 일반적으로는 불필요하다. cleaner는 finalizer보다 덜 위험하지만, 여전히 예측하기 어렵고 느리고 일반적으로는 불필요하다.

 

피해야 하는 이유

1. 언제 실행될지 알 수가 없어서 제때 실행되어야 하는 작업은 절대 할 수 없다.

2. 상태를 영구적으로 수정하는 작업에서는 절대 사용하면 안된다.

 DB lock해제를 finalizer나 cleaner에게 맡겨놓는다면 분산 시스템 전체가 서서히 멈출 것이다.

3. 심각한 성능 문제도 동반한다.

4. finalizer공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다.

생성자나 직렬화 과정에서 예외가 발생하면, 이 생성되다 만 객체에서 악의적인 하위 클래스의 finalizer가 수행 될 수 있게 된다. finalizer를 통해 허용되지 않을 작업을 수행 할 수 있다. finalizer가 있는 객체는 finalizer를 final로 만들어 아무 일도 하지 않게 만들자.

 

대안책

AutoCloseable을 구현해주고 인스턴스를 다 쓰고 나면 이 객체가 더 이상 유효하지 않음을 필드에 기록한 후 close를 호출해준다.

public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();

    private static class State implements Runnable {
        int numJunkPiles;

        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) {
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}

 

적절한 쓰임새

1. close메서드를 호출하지 않는 것에 대비한 안전망 역할이다. 나중에라도 회수하는 것이 차라리 나은 상황에 사용한다.

2. 네이티브 피어와 연결된 객체

네이티브 피어는 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말한다. 네이티브 객체는 자바 객체가 아니니 가비지 컬렉터는 그 존재를 알지 못해 cleaner나 finalizer로 처리하기 적당한 작업이다. 하지만 성능저하나 즉시 회수해야 한다면 close를 사용해야만 한다.

 

결론

최대한 피하고, try-with-resource를 쓰는 것을 원칙으로 하자.

System.exit을 호출할 때의 cleaner 동작은 구현하기 나름이다. 청소가 이뤄질지는 보장하지 않는다.

 

try-finally보다는 try-with-resource를 사용하라.

자바 라이브러리에는 close메서드를 호출해 직접 닫아줘야 하는 자원이 많다. ( InputStream, OutputStream, java.sql.conntection) 하지만 클라이언트에서 놓치기 쉬워서 예측 할 수 없는 성능 문제로 이어지기도 한다. 그래서 try-finally를 사용하여 제대로 닫힘을 보장한다.


try-finally방식

try-finally를 통해 잘 구현했지만, readLine에서 예외가 발생할 경우 close 처리하지 못한다.

static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();    // readLine이 예외를 던질 수 있다.
    } finally {
        br.close();
    }
}

자원이 둘 이상이면 지저분해진다.

static void copy(String src, String dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            byte[] buf = new byte[100];
            int n;
            while ((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}

try-with-resource를 사용하게 되면 자원의 close와 가독성있는 코드가 가능해진다. 다만, close를 자동으로 호출하는 것은 AutoCloseable 인터페이스를 구현 했을 경우이다.


try-with-resource방식

가독성이 좋아진다.

static String firstLineOfFile(String path) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}
static void copy(String src, String dst) throws IOException {
    try(InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst)) {
        byte[] buf = new byte[100];
        int n;
        while((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);
    }
}

try-catch문

static String firstLineOfFile(String path) {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    } catch (IOException e) {
        return e.getMessage();
    }
}

꼭 회수해야 하는 자원을 다룰 때는 try-finally말고, try-with-resources를 사용하자. 

 

 

728x90

'Language > Effective java' 카테고리의 다른 글

모든 객체의 공통 메서드  (0) 2022.06.06
모든 객체의 공통 메서드  (0) 2022.06.06
객체 생성과 파괴  (0) 2022.06.01
객체 생성과 파괴  (0) 2022.06.01
객체 생성과 파괴  (0) 2022.05.22

댓글