tonykang22 / study

0 stars 0 forks source link

[Effective Java] 아이템 7. 다 쓴 객체 참조를 해제하라. #47

Open tonykang22 opened 2 years ago

tonykang22 commented 2 years ago

아이템 7. 다 쓴 객체 참조를 해제하라.

핵심 정리


예시 코드


스택의 예시

// public Object pop() { // if (size == 0) // throw new EmptyStackException(); // return elements[--size]; // }

private void ensureCapacity() {
    if (elements.length == size)
        elements = Arrays.copyOf(elements, 2 * size + 1);
}

// 제대로 구현한 pop 메서드
public Object pop() {
    if (size == 0)
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // 다 쓴 참조 해제
    return result;
}

public static void main(String[] args) {
    Stack stack = new Stack();
    for (String arg : args)
        stack.push(arg);

    while (true)
        System.err.println(stack.pop());
}

}


<br>

### 캐시 (WeakHashMap)
- 주석 참고
- Key를 굳이 객체를 만들어 할당한 이유
    * 가령 Integer를 key로 직접 사용한다면 객체 할당 해제가 되지 않는다.
    * JVM 내부에 Constant pool 등에 cache되기 때문이다.

``` java
public class PostRepository {

    private Map<CacheKey, Post> cache;

    public PostRepository() {
        this.cache = new WeakHashMap<>();
    }

    public Post getPostById(CacheKey key) {
        if (cache.containsKey(key)) {
            return cache.get(key);
        } else {
            // TODO DB에서 읽어오거나 REST API를 통해 읽어올 수 있습니다.
            Post post = new Post();
            cache.put(key, post);
            return post;
        }
    }

    public Map<CacheKey, Post> getCache() {
        return cache;
    }
}
class PostRepositoryTest {

    @Test
    void cache() throws InterruptedException {
        PostRepository postRepository = new PostRepository();
        // cache() 메소드가 끝날 때까지 key1은 참조가 가능하다. 즉, WeakHashMap을 사용했더라도 해제되지 않는다.
        CacheKey key1 = new CacheKey(1);
        postRepository.getPostById(key1);

        assertFalse(postRepository.getCache().isEmpty());

        // CacheKey 유효기간이 끝났다고 가정한
        key1 = null;

        // TODO run gc
        System.out.println("run gc");
        System.gc(); // 코드 실행 즉시 gc가 실행한다는 보장은 없지만 테스트는 works
        System.out.println("wait");
        Thread.sleep(3000L);

        assertTrue(postRepository.getCache().isEmpty());
    }
}


Background Thread

class PostRepositoryTest {

    @Test
    void backgroundThread() throws InterruptedException {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        PostRepository postRepository = new PostRepository();
        CacheKey key1 = new CacheKey(1);
        postRepository.getPostById(key1);

        Runnable removeOldCache = () -> {
            System.out.println("running removeOldCache task");
            Map<CacheKey, Post> cache = postRepository.getCache();
            Set<CacheKey> cacheKeys = cache.keySet();
            Optional<CacheKey> key = cacheKeys.stream().min(Comparator.comparing(CacheKey::getCreated));
            key.ifPresent((k) -> {
                System.out.println("removing " + k);
                cache.remove(k);
            });
        };

        System.out.println("The time is : " + new Date());

        executor.scheduleAtFixedRate(removeOldCache,
                1, 3, TimeUnit.SECONDS);

        Thread.sleep(20000L);

        executor.shutdown();
    }

}


리스너, 콜백

유저가 방을 나갈 때 user가 해지되지 않는 경우

public class ChatRoom {

    // 올바르지 않은 WeakReference 사용법 (뒤에서 다시 정리)
    private List<WeakReference<User>> users;

    public ChatRoom() {
        this.users = new ArrayList<>();
    }

    public void addUser(User user) {
        this.users.add(new WeakReference<>(user));
    }

    public void sendMessage(String message) {
        users.forEach(wr -> Objects.requireNonNull(wr.get()).receive(message));
    }

    public List<WeakReference<User>> getUsers() {
        return users;
    }
}
class ChatRoomTest {

    @Test
    void charRoom() throws InterruptedException {
        ChatRoom chatRoom = new ChatRoom();
        User user1 = new User();
        User user2 = new User();

        chatRoom.addUser(user1);
        chatRoom.addUser(user2);

        chatRoom.sendMessage("hello");

        user1 = null;

        System.gc();

        Thread.sleep(5000L);

        List<WeakReference<User>> users = chatRoom.getUsers();
        assertTrue(users.size() == 1);
    }

}


완전 공략


완벽 공략 19. NullPointerException

Java 8 Optional을 활용해서 NPE를 최대한 피하자.


완벽 공략 20. WeakHashMap

더이상 사용하지 않는 객체를 GC할 때 자동으로 삭제해주는 Map


완벽 공략 21. ScheduledThreadPoolExecutor

Thread와 Runnable을 학습했다면 그 다음은 Executor.