2023. 11. 19. 19:14ㆍBook/이펙티브 자바
들어가기 전 요약
- 반납할 자원이 있는 클래스는 AutoCloseable을 구현하고 클라이언트에서 close()를 호출하거나 try-with-resource를 사용해야 한다.
자바는 두가지 객체 소멸자를 제공한다.
1. finalizer : 예측할 수 업고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다
2. cleaner : finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.
단점
- finalizer와 cleaner는 즉시 수행된다는 보장이 없다.
- finalizer와 cleaner는 실행되지 않을 수도 있다.
- finalizer 동작 중에 예외가 발생하면 정리 작업이 처리되지 않을 수도 있다.
- finalizer와 cleaner는 심각한 성능 문제가 있다.
- finalizer는 보안 문제가 있다.
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);
}
}
=====================================================================================
public class BrokenAccount extends Account{
public BrokenAccount(String accountId) {
super(accountId);
}
@Override
protected void finalize() throws Throwable {
this.transfer(BigDecimal.valueOf(100000), "son");
}
}
Account 클래스가 있고 특정 계좌 주인의 이름에는 에러를 던지는 조건이 있다고 했을 때 해당 클래스를 상속해서 finalize를 오버라이딩한 클래스를 실행시면 어떻게 되는지 보자
@Test
void 푸티_공격_계정() throws InterruptedException {
Account account = null;
try {
account = new BrokenAccount("푸티");
} catch (Exception e) {
System.out.println("이러면???");
}
System.gc();
Thread.sleep(3000L);
//account.transfer(BigDecimal.valueOf(10.4), "hello");
}
==>
이러면???
transfer 100000.000000 from 푸티 to son
부모 클래스의 생성자 메서드를 호출해 에러가 발생해도 상속받은 클래스의 finalize 메서드가 호출되어 심각한 공격을 받을 수 있다.
이러한 공격을 막으려면 finalize 클래스를 부모 클래스에서 final로 오버라이딩해 자식 클래스가 더이상 오버라이딩 하지 못하게 해야한다.
쓰임새
- 자원의 소유자가 close 메서드를 호출하지 않는 것에 대비한 안전망 역할 : 즉시 회수된다는 보장은 없지만 지연 회수를 하는 것이 안하는 것만 낫다
// 코드 8-1 cleaner를 안전망으로 활용하는 AutoCloseable 클래스 (44쪽)
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();
}
}
=======================================================================================
close() 메서드 호출
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");
//이런식으로 가비지 컬렉터를 강제로 호출하면 안된다
//System.gc();
}
}
State 메서드는 중첩클래스이기 때문에 static으로 선언했다. 그렇지 않다면 바깥 클래스인 Room의 레퍼런스르 가지고 있기 때문에 순환참조가 일어날 수 있다.
- 네이티브 피어(자바 객체가 아님)와 연결된 객체에서 가비지 컬렉터가 그 존재를 알지 못하니 객체 소멸자를 이용해 회수한다.(단, 성능 저하를 감당할 수 있고 네이티브 피어가 심각한 자원을 가지고 있지 않을 때에만 해당된다.) 웬만하면 close() 메서드를 사용하자
'Book > 이펙티브 자바' 카테고리의 다른 글
Item10 - equals는 일반 규약을 지켜 재정의하라(1) (1) | 2023.11.20 |
---|---|
Item9 - try ~ finally 보다 try-with-resources를 사용하라 (1) | 2023.11.19 |
Item7 - 다 쓴 객체 참조를 해제하라 (0) | 2023.11.18 |
Item6 - 불필요한 객체 생성을 피하라 (0) | 2023.11.17 |
Item5 - 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.11.16 |