본문 바로가기

IT/자바

[Effective Java] item 8, 9 finalizer & cleaner 사용을 피해라 / try-finally 보다는 try-with-resources를 사용해라

반응형

자바는 두가지 객체 소멸자를 제공한다.

1) finalizer

2) cleaner 

 

finalizer 는 예측할 수 없고 상황에 따라 위험할 수 있어 사용을 권장하지 않는다. 반면에 cleaner는 finalizer 보단 안전하지만 여전히 예측할 수 없음으로 사용을 권장하지 않는다.

 

해결방법으론 item 9인 try-with-resources 를 사용하는 것인데 먼저 왜 사용하면 안되는지 설명하고자 한다.

 

문제점 1

finalizer, cleaner은 언제 수행될지 알기 어렵다.

 

왜냐하면 finalizer 쓰레드는 우선순위가 낮기 때문에 실행될 기회를 자꾸 놓치고 cleaner 스레드는 동일한 순위에 있지만 백그라운드에서 수행되고 가비지컬렉터의 통제를 받기 때문에 즉각 수행됨을 보장하지 못한다.

 

수행 시점 뿐만 아니라 수행 여부도 보장하지 않다.

 

문제점 2

finalizer 수행 중에 예외가 발생하면 그 예외는 무시되고 남은 작업는 수행되지 못한 상태로 종료된다. (예외 경고 메세지 없음) - cleaner는 다행히 예외 추적 가능

 

문제점 3 

심각한 성능  이슈가 있다.

일반적인 AutoCloseable 객체를 생성해서 가비지 컬랙터가 수거하는거까지 12ns 가 걸리는 반면에 finalizer를 일반적인 안전망 형태(= 생성 - > 정리 -> 파괴) 로 사용하더라도 66ns 약 5배나 느려진다.

 

문제점 4

finalizer를 사용한 클래스는 finalizer  공격에 노출되어 심각한  보안 문제를 일으킬 수 있다.

생성자나 직렬화 과정에서 예외가 발생하면 생성되다 만 객체가 악의적인 하위 클래스의 finalizer가 수행될 수 있다.

 

위와 같은 문제점에도 불구하고 finalizer가 존재하는 이유는 바로 안전망 역할 때문이다.

 

즉시 호출될 보장은 없지만 클라이언트가 하지 않는 자원 회수를 늦게라도 수행해주기 때문이다. 

또한 네이티브 피어는 자바 객체가 아니니 가비지 컬렉터는 그 존재를 알지 못한다. 그러므로 자바 객체를 회수하더라도 네이티브 객체까지 회수하지 못한다. 이 경우 finalizer를 통해 네이티브 객체 회수를 가비지 컬렉터에 알려야 한다.

더보기

네이티브 피어 : 일반 자바 객체가 네이티브 메서드를 통해 기능을 위임한 네이티브 객체를 말함.

 

그러므로 보통 직접 자원을 닫아줘야 하는 InputStream, OutputStream, java.sql.Connection 같은 경우 finalizer를 사용해야 되는데 

public class FileUtil {

  private static final int BUFFER_SIZE = 1024;

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

이 처럼 코드가 복잡해지거나, 예외를 추적하지 못하는 경우가 발생할 수 있다.

그 외 여러문제로 try-with-resources 방식을 사용하길 권장한다.

 

[Try-with_resources 방식]

public class FileUtil {

  private static final int BUFFER_SIZE = 1024;

  public static String getFirstLineOfFile(String filepath) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
      return br.readLine();
    }
  }

  public static void copy(String src, String dest) throws IOException {
    try (InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dest);) {
      byte[] buf = new byte[BUFFER_SIZE];
      int n;
      while ((n = in.read(buf)) >= 0) {
        out.write(buf, 0, n);
      }
    }
  }
}
public static String getFirstLineOfFile(String filepath) {
  try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
    return br.readLine();
  }
  catch (IOException e) {
    e.printStackTrace();
    return "";
  }
}
반응형