file 처리 시에 생성되는 Filehandler(Id). 실제 파일 엑세스 및 처리할 수 있는 file 개수가 정해져있다.
큰 회사도 사용할 수 있는 소켓 수가 정해져있어.
자원은 유한하기에 자원 반납이 잘 이뤄져야한다.
소멸 과정의 내용, 통신 connection, db 커넥션 등을 정리하지 않으면 계속해서 resource를 사용하게 된다.
finalizer > cleaner 더 위험. 사용하지 않는게 옳다.
finalizer와 cleaner는 즉시 수행된다는 보장이 없다.
애초에 finalizer에 접근을 할 수 없다. (public level의 클래스가 아니라 package level의 클래스로 참조해서 사용할 수 없다.)
ReferenceQueue가 존재한다. (Reflection을 통해서 접근은 가능하다.)
finalizer는 자바 11부터 deprecated되기는 했다.
cleaner는 접근해서 사용은 가능하다.
finalizer와 cleaner는 실행되지 않을 수도 있다.
내부의 큐를 수행하는 우선순위보다 객체를 만드는 우선순위가 낮기 때문이다.
finalize 동작 중에 에외가 발생하면 정리 작업이 처리되지 않을 수도 있다.
자원 반납이 되지 않고 작업이 처리될 수도 있다.
finalizer와 cleaner는 심각한 성능 문제가 있다.
finalizer는 보안 문제가 있다
반납할 자원이 있는 클래스는 AutoCloseable을 구현하고 클라이언트에서 close()를 호출하거나 try-with-resource를 사용해야 한다.
Cleaner 사용법
자원 반납용 안전망으로 사용할 수 있다.
(try-with-resource로 사용하기를 기대하고 만들었으나 사용자가 어떻게 사용할지 모르기 때문에)
기본적으로 phantom reference를 사용한다.
호출되리라는 보장이 없지만, 없는 것 보다는 나을 수 있다.
내부 클래스로 만들거라면 static 클래스로 만들어야 한다.
static 클래스로 만들지 않으면, 인스턴스를 통해 cleaner에 접근해야해서 객체가 부활하게 되기 때문이다.
네이티브 피어 자원 회수
네이티브 피어 자원이란, 자바 모든 코드 중 일부는 네이티브한 라이브러리에 접근해야하는 경우가 있다. (C, C## 등, OS에 접근해야하는 경우가 될 수도 있겠다. 자바 피어(e.g. J-Frame, Java Swing Library 등) 자원도 존재한다.)
단, 성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때에만 해당한다.
네이티브 피어가 사용하는 자원을 즉시 회수해야 한다면 close() 메소드를 호출 해야 한다.
Clean을 하는 runnable 안에서는 clean의 대상이 되는 객체의 레퍼런스가 존재하면 절대 안된다.
해당 작업은 gc가 진행되며 객체가 없어질 때 수행되는 작업인데, 객체를 참조하면 객체가 부활할 수 있다. (없어졌다가 다시 살아날 수 있다.)
객체가 아닌 객체의 resource를 참조하도록 해야한다.
예시 코드
Cleaner를 안전망으로 사용하는 Room Class
안전망 갖춘 자원을 제대로 활용하는 Adult Class (try-with-resource)
안전망 갖춘 자원을 제대로 활용하지 못하는 Teenager Class
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다!
private static class State implements Runnable {
int numJunkPiles; // Number of junk piles in this room
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// close 메서드나 cleaner가 호출한다.
@Override public void run() {
System.out.println("Cleaning room");
numJunkPiles = 0;
}
}
// 방의 상태. cleanable과 공유한다.
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("안녕~");
}
}
}
public class Teenager {
public static void main(String[] args) {
new Room(99);
System.out.println("Peace out");
// gc를 수행하면 자원을 반납하겠으나, 아래와 같이 gc를 강제로 호출 하는 방식에 의존해서는 절대 안된다.
System.gc();
}
}
완벽 공략
p42, Finalizer 공격
p43, AutoClosable
p45, 정적이 아닌 중첩 클래스는 자동으로 바깥 객체의 참조를 갖는다.
p45, 람다 역시 바깥 객체의 참조를 갖기 쉽다.
예시 코드 (정적이 아닌 중첩 클래스는 자동으로 바깥 객체의 참조를 갖는다.)
InnerClass에서 OuterClass를 참조할 때 - OuterClass.this.로 참조할 수 있다.
public class OuterClass {
private void hi() {
}
class InnerClass {
public void hello() {
OuterClass.this.hi();
}
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
InnerClass innerClass = outerClass.new InnerClass();
System.out.println(innerClass);
outerClass.printFiled();
}
private void printFiled() {
Field[] declaredFields = InnerClass.class.getDeclaredFields();
for(Field field : declaredFields) {
System.out.println("field type:" + field.getType());
System.out.println("field name:" + field.getName());
}
}
}
예시 코드 (람다 역시 바깥 객체의 참조를 갖기 쉽다.)
public class LambdaExample {
private int value = 10;
// LambdaExample을 정리하기 위한 메소드를 이런 방식
// (Capturing하는 방식. 즉, 바깥 객체의 참조를 갖도록 하는 방식, `value`)
// 으로 두게 되면 바깥 객체에 대한 참조가 생기기 때문에 객체가 부활하게 된다.
private Runnable instanceLambda = () -> {
System.out.println(value);
};
public static void main(String[] args) {
LambdaExample example = new LambdaExample();
Field[] declaredFields = example.instanceLambda.getClass().getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("field type: " + field.getType());
System.out.println("field name: " + field.getName());
}
}
}
완벽 공략. Finalizer 공격
만들다 만 객체를 finalize 메소드에서 사용하는 방법
Finalizer 공격
상속받아 finalize 메소드를 오버라이딩한다.
gc
방어하는 방법
final 클래스로 만드는 방법
finalizer() 메소드를 오버라이딩 한 다음 final을 붙여서 하위 클래스에서 오버라이딩 할 수 없도록 막는 방법 (상속은 가능하겠으나, 메소드를 Overriding할 수 없게 만드는 방법이다.)
예시 코드
Test 도둑_계정() 코드 참조, 생성자를 호출하면, 객체를 만들다가 예외 처리로 넘어가게 되고 결국 gc가 수행될 때, transfer이 수행되게 된다.
public class Account {
private String accountId;
public Account(String accountId) {
this.accountId = accountId;
if (accountId.equals("도둑")) {
throw new IllegalArgumentException("도둑은 계정을 막습니다.");
}
}
public void transfer(BigDecimal amount, String to) {
System.out.printf("transfer %f from %s to %s\n", amount, accountId, to);
}
}
아이템 8. finalizer와 cleaner 사용을 피하라
핵심 정리
Cleaner 사용법
예시 코드
완벽 공략
예시 코드 (정적이 아닌 중첩 클래스는 자동으로 바깥 객체의 참조를 갖는다.)
InnerClass에서 OuterClass를 참조할 때 -
OuterClass.this.
로 참조할 수 있다.예시 코드 (람다 역시 바깥 객체의 참조를 갖기 쉽다.)
완벽 공략. Finalizer 공격
만들다 만 객체를 finalize 메소드에서 사용하는 방법
예시 코드
Test
도둑_계정()
코드 참조, 생성자를 호출하면, 객체를 만들다가 예외 처리로 넘어가게 되고 결국 gc가 수행될 때, transfer이 수행되게 된다.-> 참고 : 테스트 실행 시 출력
완벽 공략. AutoClosable
try-with-resource를 지원하는 인터페이스
catch(IOException e)
)