Programing/Java

Effective Java - 객체 소멸

안중환 2016. 3. 4. 14:59
  • 1_명시적 자원 해지

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.awt.Image;
    import java.io.IOException;
    import java.net.URL;
    
    import javax.imageio.ImageIO;
    
    // 핵심 : 객체 내부에서 비 메모리 자원을 사용한다면 명시적 종료 메소드를 제공해야 한다.
    // 이유
    // 1. 가비지 컬렉션은 메모리만 수집한다.
    // 2. 가비지 컬렉션이 수거하지 않는 비 메모리 자원에 대해서는
    // 프로그래머가 관리 해야 한다.
    
    // 3. finalize() - 종료자
    // 개념 : 더 이상 참조할 수 없는 객체의 메모리 공간을 회수 할 때 GC에 의해서 호출되는 메소드
    // 문제 : finalize()를 비 메모리 자원을 정리하는 용도로 사용하면 안된다.
    // 1. 즉시 실행된다는 보장이 없다.
    // 예) 종료자 안에서 파일 닫기. - JVM은 종료자를 천천히 실행하므로,
    // 열린 상태의 파일이 많이 남아 있을 수 있다.
    // 한번에 열 수 있는 파일의 개수에 제한이 있으므로 오류가 날 수 있다.
    // - 종료자의 실행 시점은 JVM의 구현에 의존한다.
    
    // 2. 반드시 실행된다는 보장도 없다.
    // - 자바 명세에는 종료자가 즉시 실행되어야 한다는 문구도 없지만,
    // 종료자가 반드시 실행되어야 한다는 문구도 없다.
    // 즉 종료자가 실행되지 않은 객체가 남은 상태로 프로그램이 종료할 수도 있다.
    // (동기화 객체 같은 것을 절대 종료자를 통해 반납하면 안된다)
    
    
    class WebPhoto {
      Image image;
    
      // 명시적인 종료 메소드 - OutputStream, InputStream, Socket 등
      public void release() {
        if (image != null) {
          image.flush();
        }
      }
    
      public WebPhoto(String imageUrl) {
        URL url;
        try {
          url = new URL(imageUrl);
          image = ImageIO.read(url);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    
    
    public class Example1 {
      public static void main(String[] args) throws InterruptedException {
        // WebPhoto가 더 이상 사용되지 않는다면, 객체 내부에서 사용하고 있는
        // 자원에 대해서 정리가 필요하다.
        WebPhoto photo = new WebPhoto("http://cfs7.tistory.com/image/14/tistory/2008/08/29/04/53/48b702410053a");
    
        // photo = null;
        // photo.release();
    
        // System.gc();
      }
    }
    

  • 2_finalize

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.awt.Image;
    import java.io.IOException;
    import java.net.URL;
    
    import javax.imageio.ImageIO;
    
    // finalize()의 용도
    // : 명시적 종료 메소드 호출을 잊은 경우의 안정망을 제공할 수 있다.
    
    // 1. 그런 자원을 발견한 경우 반드시 경고 메세지를 남겨야 한다. (클라이언트 코드에 버그가 있는 것이므로)
    // 2. 부모의 종료자를 명시적으로 호출해야 한다.
    
    class WebPhoto {
      Image image;
    
      /*
      @Override
      public void finalize() {
        if (image != null) {
          System.err.println("Explicit termination method 'release' is not called");
          release();
        }
      }
      */
      
      @Override
      public void finalize() throws Throwable {
        try {
          if (image != null) {
            System.err.println("Explicit termination method 'release' is not called");
            release();
          }
        } finally {
          super.finalize();
        }
      }
      
      // 명시적인 종료 메소드 - OutputStream, InputStream, Socket 등
      public void release() {
        if (image != null) {
          image.flush();
        }
      }
    
      public WebPhoto(String imageUrl) {
        URL url;
        try {
          url = new URL(imageUrl);
          image = ImageIO.read(url);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    
    
    public class Example2 {
      public static void main(String[] args) throws InterruptedException {
        WebPhoto photo = new WebPhoto("https://t1.daumcdn.net/cfile/tistory/2761F44856D7F79F2A");
        // System.gc();
      }
    }
    

  • 3_finalize2

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.awt.Image;
    import java.io.IOException;
    import java.net.URL;
    
    import javax.imageio.ImageIO;
    
    // 명시적 자원 해지가 필요한 클래스는 결국 중복된 코드를 작성해야 한다.
    
    // 종료자의 역활을 일반화한 클래스
    final class CloseGuard {
      public static CloseGuard get() {
        return new CloseGuard();
      }
    
      private CloseGuard() {}
    
      private Throwable site;
    
      public void open(String closer) {
        if (closer == null) throw new NullPointerException("closer == null");
    
        String message = "Explicit termination method '" + closer + "' not called";
        site = new Throwable(message);
      }
    
      public void close() {
        site = null;
      }
    
      public void warnIfOpen() {
        if (site == null) return;
    
        System.err.println(site.toString());
      }
    }
    
    
    class WebPhoto {
      private Image image;
    
      // Surface.java
      private final CloseGuard mCloseGuard = CloseGuard.get();
    
      @Override
      public void finalize() throws Throwable {
        try {
          if (mCloseGuard != null) mCloseGuard.warnIfOpen();
          release();
    
        } finally {
          super.finalize();
        }
      }
    
      public void release() {
        if (image != null) {
          image.flush();
        }
    
        if (mCloseGuard != null) mCloseGuard.close();
      }
    
      public WebPhoto(String imageUrl) {
        URL url;
        try {
          url = new URL(imageUrl);
          image = ImageIO.read(url);
    
          mCloseGuard.open("release");
    
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
    
    
    public class Example3 {
      public static void main(String[] args) {
        WebPhoto photo = new WebPhoto("https://t1.daumcdn.net/cfile/tistory/2761F44856D7F79F2A");
        // System.gc();
      }
    }
    

  • 4_finalize3
    : Finalizer Guardian Idiom(종료자 보호 패턴)
    Image를 상속 받은 클래스(MyImage)의 객체 생성 -> 상위 클래스 Image 객체도 생성 -> MyImage 객체가 GC에 의해 해지 당할 때 하 상위 클래스(Image)의 guardian이란 멤버 변수도 해지 대상이 되고, 여기서 finalize() 호출

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    
    class Image {
    
      public void release() {
        System.out.println("Image 자원 해지");
      }
    
    //  @Override
    //  protected void finalize() throws Throwable {
    //    try {
    //      System.out.println("Image finalize!");
    //      release();
    //    } finally {
    //      super.finalize();
    //    }
    //  }
    
      // 하위 클래스가 부모 클래스의 finalize()를 잊는 문제를
      // 방지하는 방법. - (종료자 보호 패턴)Finalizer Guardian Idiom
      @SuppressWarnings("unused")
      private final Object guardian = new Object() {
        @Override
        protected void finalize() throws Throwable {
          release();
        }
      };
    }
    
    class MyImage extends Image {
    
      @Override
      protected void finalize() throws Throwable {
        System.out.println("MyImage finalize!");
        // 잊었다.!!!
        // super.finalize();
      }
    }
    
    
    public class Example4 {
      public static void main(String[] args) throws InterruptedException {
        MyImage image = new MyImage();
        image = null;
    
        System.gc();
    
        Thread.sleep(10000);
      }
    }