[Effective Java] item 8 : finalizer와 cleaner 사용을 피하라

DEINGVELOP·2023년 1월 4일
0

Effective Java

목록 보기
2/19

📌 요약

  • finalizercleaner는 즉시 수행된다는 보장이 없으며, 실행되지 않을 수도 있다.

  • finalizer 동작 중에 예외가 발생하면 정리 작업이 처리되지 않을 수도 있다.

  • finalizercleaner는 심각한 성능 문제가 있다.

  • finalizer는 보안 문제가 있다.

  • 반납할 자원이 있는 클래스는 AutoCloseable을 구현하고, 클라이언트에서 close()를 호출하거나, try-with-resource를 사용해야 한다.

📌 Tip
Item 1 ~ 9 : 객체의 생성과 소멸에 관련된 내용

finalizer와 cleaner : 객체가 정리될 때 리소스를 제대로 반환하기 위한 의도로 있는 아이들


finalizer

:

  • Object가 죽을 때 자원을 반납하기 위해 만들어짐

  • cleaner보다 위험함

  • finalizer는 public class가 아닌 package 레벨 : 접근이 불가능함

  • Java 9버전부터 안 쓰는 것을 권장하고 있음

    @Deprecated(since="9")
    protected void finalize() throws throwable ...



사용 방법

public class MyClass {

	@Override
    protected void finalize() throws Throwable {
    	System.out.println("");
    }

}
  • 내가 원하는 클래스에 finalize를 override한다.
while(true) {
	new MyClass();
}
  • 만든 오브젝트가 ReferenceQueue에 들어가게 됨

참고 가능한 ref code
www.baeldung.com/java-finalizer

  • Java 11부터는 deprecated 되어서 안 쓰게 될 것임

사용시 문제점

  • 직접 돌려보면 알게 되는데, finalizer의 queue를 처리하는 스레드의 우선순위가 낮음 → 권장되지 않는 이유!

  • finalize를 잘못 쓰면, 이 finalizer 안에서 다른 오브젝트를 참조하거나 자기 자신을 또 참조 또는 인스턴스를 만들어 쓰게 되면 오브젝트가 결국 또 다시 생기게 됨



cleaner

:

  • Java 9에서 추가되었고, finalizer 대신 권장되는 기능

사용방법

public class BigObject {
	
    private List<Object> resource;
    
    public BigObject(List<Object> resource) { this.resource = resource }
    
    public static class ResourceCleaner implements Runnable {
    	
        private List<Object> resourceToClean;
        
        public ResourceCleaner(List<Object> resourceToClean) {
        this.resourceToClean = resourceToClean;
        }
        
        @Override
        public void run() {
			resourceToClean = null;
            System.out.println("cleaned up.");
        }
    }
}
  • finalizer에서 하던 일을 ResourceCleaner에서 한다고 보면 됨
public class CleanerIsNotGood {
	public static void (String[] args) throws InterruptedException{
    Cleaner cleaner = Cleaner.create();
    
    List<Object> resourceToCleanUp = new ArrayList<>();
    BigObject bigObject(bigObject, new BigObject.ResourceCleaner(resourceToCleanUp));
    
    bigObject = null;
    System.gc();
    Thread.sleep(mills: 3000L);
    }
}

cleaner를 사용할 때는?

  • 안전망 : 사람들이 try resources를 쓰지 않더라도 gc가 될 때 제대로 되기 위해서

  • 자원이 반납될 수 있는 기회를 가질 수 있도록

  • 호출되리라는 보장은 없지만 그래도 기회를 준다.

import java.lang.ref.Cleaner;

public class SampleResource implements AutoCloseable{

    private static final Cleaner cleaner = Cleaner.create();

    private final Cleaner.Cleanable cleanable;

    private final ResourceCleaner resourceCleaner;

    public SampleResource(final int numJunkFiles) {
        this.resourceCleaner = new ResourceCleaner(numJunkFiles);
        cleanable = cleaner.register(this, state); // Runnable 객체를 등록
    }

		// Cleanable은 별도의 쓰레드로 clean을 함.
    // SampleResource을 참조하면 순환참조가 되어버림.
    // 서로를 계속 참조하기 때문에 gc에의해 수거되지 않는다.
    // 정적클래스가 아니면 자동으로 바깥객체의 참조를 가짐. 
    private static class ResourceCleaner implements Runnable {
        int numJunkPiles; // clean할 대상

        public ResourceCleaner(final int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }

        @Override
        public void run() { // 1. close를 호출할 때, 2. cleaner(안전망)
            System.out.println("방청소");
            numJunkPiles = 0;
        }
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }
}
public class Room implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    
    // 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다! 
    private static class State implements Runnable {
        int numJunkPiles; // 방(Room) 안의 쓰레기 수
        
        State(int numJunkPiles) {
            this.numJunkPiles = numJunkPiles;
        }
        
        // close 메서드나 cleaner가 호출된다.
        @Override public void run() {
            Syste.out.println("방 청소");
            numJunkPiles = 0;
        }
    }
    
    // 방의 상태. cleannable과 공유한다.
    private final State state;
    
    // cleanable 객체. 수거 대상이 되면 방을 청소한다. 
    private final Cleaner.Cleanable cleanable;
    
    public Room(int numJunkPiles) {
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }
    
    @Override public void close() {
        cleanable.clean();
    }
}
public class Adult {
    public static void main(String[] args) {
        try (Room myRoom = new Room(7)) {
            System.out.println("안녕~");
        }
    }
}
  • AutoCloseable로 자원 반환하는 코드
public class Teenager {
    public static void main(String[] args) {
        new Room(99);
        System.out.println("아무렴");
    }
}
  • close 메소드로 자원 반환하는 코드

AutoCloseable

: 권장되는 사용법

public class AutoClosableIsGood implements AutoCloseable {

	private BufferedInputStream inputStream;
    
    @Override
    public void close() {
    	try {
        	inputStream.close();
        } catch (IOException e) {
        	throw new RuntimeException("failed to close " + inputStream);
        }
    }
}
public class App {

	public static void main(String[] args) {
    	try(AutoCloseableIsGood good = new AutoClosableIsGood()) {
        // TODO 자원 반납 처리가 됨
        }
    }
}

finalizer 공격

public class BrokenAccount extends Account {
	public BrokenAccount () {
    	this.
	}
}

0개의 댓글