Jake Seo
제이크서 개발 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (719)
    • AI 서비스 개발 일기 (3)
    • LLM 개발 일기 (1)
    • ------레거시 (2025.08.23 이전)--.. (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 느린 쿼리
  • 도커공식문서
  • 이펙티브 자바
  • 자바스크립트
  • 싱글턴
  • 슬로우 쿼리
  • 객체복사
  • 외래키 제약조건
  • 자바 디자인패턴
  • MSSQL
  • next js app
  • item7
  • prerendering
  • rust
  • try-with-resources
  • serverless computing
  • 메이븐 골
  • 자바스크립트 면접
  • 자바
  • 디자인패턴
  • 스프링 검증
  • Pre-rendering
  • Next.js
  • 메이븐 페이즈
  • 자료구조
  • 자바 검증
  • Javadoc 자바독 자바주석 주석 Comment
  • 자바스크립트 인터뷰
  • NEXT JS
  • 알고리즘
  • 러스트
  • 추상 팩터리 패턴
  • 빈 검증
  • 메이븐 라이프사이클
  • item9
  • Java
  • 싱글톤 패턴
  • 팩터리 메서드 패턴
  • 프로그래머의 뇌
  • 플라이웨이트패턴
  • 작업기억공간
  • 토비의 스프링
  • bean Validation
  • item8
  • pnpm
  • 서버리스 컴퓨팅
  • 이펙티브 자바 item9
  • 싱글톤
  • 참조 해제
  • 이펙티브자바

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 개발 블로그

Java/이펙티브 자바

이펙티브 자바, 쉽게 정리하기 - item8. finalizer와 cleaner 사용을 피하라

2021. 12. 27. 16:42

finalizer와 cleaner의 사용을 피하라

자바가 제공하는 객체 소멸자

  • 자바에서는 2가지 객체 소멸자를 제공한다. finalizer와 cleaner이다.
    • 그러나, 이 두 소멸자는 기본적으로 쓰지 말아야 한다.

이후에 나오지만 try-catch-with-resources 방식이 권장된다.

finalizer와 cleaner를 지양해야 하는 이유

  • 가비지 컬렉터에 의해 실행이 결정되며, 즉시 실행된다는 보장은 없다.
    • 객체에 접근하지 못하게 된 뒤로 finalizer나 cleaner가 실행되는데 얼마나 소요되는지 알 수 없다.

finalizer와 cleaner가 즉시 실행된다는 보장이 없을 때 생기는 문제

  • ex) 시스템이 동시에 열 수 있는 파일의 갯수는 한정되어 있다.
    • 열었던 파일을 닫아주지 않으면, 더이상 새로운 파일을 열지 못한다.
    • finalizer나 cleaner는 실행 시점이 불명확하기 때문에, 파일이 정말 닫혔는지 알 수 없다.
    • 이로 인해 많은 에러가 발생 가능하다.

자바는 심지어 finalizer와 cleaner의 실행 여부조차 보장해주지 않는다.
즉, 실행이 안 될 수도 있다는 뜻이다.

상태를 영구적으로 수정하는 작업에서는 특히 절대로 finalizer와 cleaner에 의존해선 안된다.

  • DB 공유 자원의 lock 해제와 같은 작업은 명시적인 시점에 분명하게 이루어져야 한다.
    • lock이 제때 해제되지 않으면, 공유 자원을 필요로 하는 다른 작업들이 전부 lock이 풀리기만 기다리고 분산 시스템은 멈추게 될 것이다.

System.gc()와 System.runFinalization() 메서드에 현혹되지 말자. 이 메서드들도 여전히 문제를 해결해주지 못한다.

finalizer의 동작 특징에서 발생하는 또 다른 문제

  • finalizer는 처리할 작업이 남았을 때에도 스레드를 종료시킨다.
    • 해당 객체는 마무리가 덜 된 상태로 남게 되고, 다른 스레드가 이 객체를 다시 사용하려 하면 어떤 동작이 나타날지 예측할 수 없다.
    • 예외조차 출력되지 않으며 이상 현상이 발생할 수 있다.

cleaner는 그나마 위와 같은 문제는 없다.

finalizer와 cleaner가 갖고오는 성능 문제

예제 코드를 돌렸을 때 리소스 회수에 각각 아래와 같은 시간이 걸렸다.

  • AutoClosable 구현 (try-with-resources): 12ns
  • finalizer: 550ns
  • cleaner: 500ns

단 cleaner와 같은 경우 직접 리소스 회수 외에 안전망 형태로만 사용할 수도 있는데, 이 경우엔 50ns로 상대적으로 매우 빠르다.

finalizer가 갖고 오는 보안 문제

  • 생성자나 직렬화 과정에서 예외가 발생했을 때, 생성되다만 객체에서 악의적인 하위 클래스의 finalizer가 수행될 수 있다.
  • finalizer는 정적 필드에 자신의 참조를 할당하여, 가비지 콜렉터의 회수를 피해갈 수도 있다.
  • 객체 생성을 막을 때, 생성자에서 예외를 던지는 것만으로는 finalizer 보안 문제를 막을 수 없다.
    • 이 문제를 막으려면 아무일도 하지 않는 final로 선언된 finalizer 메서드를 생성해놓으면 된다.
  • final 클래스의 경우에는 하위 클래스를 만들 수 없기 때문에 이러한 문제에서 자유롭다.

fianlizer와 cleaner 대신 AutoClosable 인터페이스를 구현하자

  • AutoClosable 인터페이스를 구현할 때는 close() 메서드에 try-with-resources를 주로 사용한다.
  • close() 메서드에서는 추가적으로 필드에 이 객체가 닫혔는지 기록해두는 것이 좋다.
    • 닫힌 객체에 접근하면, IllegalStateException을 던져주자.

cleaner와 finalizer의 용도

  • 일반적인 리소스 회수는 try-with-resources를 통해 AutoClosable인터페이스를 구현하는 게 낫다고 배웠다.
  • cleaner와 finalizer는 .close() 메서드를 호출하지 않았을 때를 대비해 안전망 역할로 제공할 수 있다.
    • FileInputStream, FileOutputStream, ThreadPoolExecutor가 이러한 방식을 사용하고 있다.
  • 네이티브 피어와 연결된 객체를 회수할 때도 cleaner와 finalizer를 사용할 수 있다.
    • 네이티브 피어란 네이티브 메서드를 통해 기능을 위임한 네이티브 객체인데 JVM에 의해 발견되지 않아 자동회수가 되지 않는다.

cleaner를 안전망으로 활용하는 예제 코드

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

    private static class State implements Runnable {
        int numJunkPile;

        public State(int numJunkPile) {
            this.numJunkPile = numJunkPile;
        }

        @Override
        public void run() {
            System.out.println("방 청소");
            numJunkPile = 0;
        }
    }

    private final State state;

    private final Cleaner.Cleanable cleanable;

    public Room(int numJunkPile) {
        this.state = new State(numJunkPile);
        cleanable = cleaner.register(this, state);
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
  }
}
  • 좀 더 현실성 있게 만들자면 int numJunkPile이 아니라, final long nativePeer와 같이 만들 수도 있다.
  • 위와 같이 Cleaner를 넣어주면, 이 Room 객체가 phantom reachable 상태가 됐을 때, cleaning action인 State 내부 run 메서드가 호출될 것이다.
    • 물론 명시적으로 .close()를 호출하거나, try-with-resources에 의해서 호출될 때도 cleaning action은 수행된다.

관련 공식문서 링크를 참조하자.
Phantomly Reachable 상태에 대해 잘 모른다면 네이버의 기술 블로그 포스팅을 참고하는 것도 도움이 많이 될 것이다.

코드 테스트해보기

@Test
public void tryWithResourcesTest() {
    try(Room room = new Room(100)) {
        System.out.println("하이");
    } catch (Exception e) {
        e.printStackTrace();
    }
}

@Test
public void useCleaner() throws Exception {
    Room room = new Room(100);
    System.gc();
    System.out.println("하이");
}
  • tryWithResourcesTest()에서는 "방 청소"가 잘 출력되었다.
  • useCleaner는 어떻게든 출력해보려고 System.gc()도 이용해보았지만, 출력되지 않았다.
    • 자바 공식문서에 따르면, 인스턴스가 phantom reachable되었을 때 호출된다고만 한다.

정리

  • cleaner(자바8까지는 finalizer)는 오직 네이티브 자원 회수 용도 혹은 안전망 역할로만 활용하자.
  • 대신 AutoCloseable을 구현하고, try-with-resources를 적극 활용하자.
저작자표시 (새창열림)

'Java > 이펙티브 자바' 카테고리의 다른 글

이펙티브 자바, 쉽게 정리하기 - item 10. equals는 일반 규약을 지켜 재정의하라  (0) 2021.12.29
이펙티브 자바, 쉽게 정리하기 - item9. try-finally보다는 try-with-resources를 사용하라  (0) 2021.12.28
이펙티브 자바, 쉽게 정리하기 - item7. 다 쓴 객체 참조를 해제하라  (0) 2021.12.27
이펙티브 자바, 쉽게 정리하기 - item6. 불필요한 객체 생성을 피하라  (0) 2021.12.26
이펙티브 자바, 쉽게 정리하기 - item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라  (0) 2021.12.25
    'Java/이펙티브 자바' 카테고리의 다른 글
    • 이펙티브 자바, 쉽게 정리하기 - item 10. equals는 일반 규약을 지켜 재정의하라
    • 이펙티브 자바, 쉽게 정리하기 - item9. try-finally보다는 try-with-resources를 사용하라
    • 이펙티브 자바, 쉽게 정리하기 - item7. 다 쓴 객체 참조를 해제하라
    • 이펙티브 자바, 쉽게 정리하기 - item6. 불필요한 객체 생성을 피하라
    Jake Seo
    Jake Seo
    ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바