ITEM 7: Eliminate Object Reference

์ž๋ฐ”๋Š” ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ๋‹ค ์“ด ๊ฐ์ฒด๋ฅผ ์•Œ์•„์„œ ํšŒ์ˆ˜ํ•ด๊ฐ„๋‹ค๊ณ  ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์— ๋” ์ด์ƒ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š์•„๋„ ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = 0;
    }

    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);
        }
    }
}

์œ„ ์Šคํƒ ์ฝ”๋“œ์—๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฌธ์ œ์ ์ด ์žˆ๋‹ค. ์œ„์˜ ์Šคํƒ์„ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์˜ค๋ž˜ ์‹คํ–‰ํ•˜๋ฉด, ์ ์ฐจ GC ํ™œ๋™๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๋Š˜์–ด๋‚˜ ๊ฒฐ๊ตญ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ๊ฒƒ์ด๋‹ค. ์ƒ๋Œ€์ ์œผ๋กœ ๋“œ๋ฌธ ๊ฒฝ์šฐ์ด์ง€๋งŒ ์‹ฌํ•  ๋•Œ๋Š” ๋””์Šคํฌ ํŽ˜์ด์ง•์ด๋‚˜ OutOfMemoryError๋ฅผ ์ผ์œผ์ผœ ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ์ข…๋ฃŒ๋˜๊ธฐ๋„ ํ•œ๋‹ค.

์œ„ ์Šคํƒ์€ ์Šคํƒ์ด ๋Š˜์—ˆ๋‹ค๊ฐ€ ์ฃผ๋Š” ๊ฒฝ์šฐ์— ์Šคํƒ์—์„œ ๊บผ๋‚ด์ง„ ๊ฐ์ฒด๋“ค์„ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ํšŒ์ˆ˜ ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ด ์Šคํƒ์ด ๊ทธ ๊ฐ์ฒด๋“ค์˜ ๋‹ค ์“ด ์ฐธ์กฐ(obsolete reference: ์•ž์œผ๋กœ ๋‹ค์‹œ ์“ฐ์ง€ ์•Š์„ ์ฐธ์กฐ)๋ฅผ ์—ฌ์ „ํžˆ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๊ฐ์ฒด ์ฐธ์กฐ ํ•˜๋‚˜๋ฅผ ์‚ด๋ ค๋‘๋ฉด GC๋Š” ๊ทธ ๊ฐ์ฒด๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๊ทธ ๊ฐ์ฒด๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ํšŒ์ˆ˜ํ•ด๊ฐ€์ง€ ๋ชปํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ๋‹จ ๋ช‡ ๊ฐœ์˜ ๊ฐ์ฒด๊ฐ€ ๋งค์šฐ ๋งŽ์€ ๊ฐ์ฒด๋ฅผ ํšŒ์ˆ˜๋˜์ง€ ๋ชปํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ณ , ์ž ์žฌ์ ์œผ๋กœ ์„ฑ๋Šฅ์— ์•…์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.

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

์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด null ์ฒ˜๋ฆฌ ๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ์ฐธ์กฐ ํ•ด์ œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์—ฌ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค. ๋‹ค ์“ด ์ฐธ์กฐ๋ฅผ null ์ฒ˜๋ฆฌํ•˜์—ฌ ์‹ค์ˆ˜๋กœ ํ•ด๋‹น ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์‹œ์Šคํ…œ์€ ์ฆ‰์‹œ NullPointException ์„ ๋˜์ง€๋ฉฐ ์ข…๋ฃŒ๋˜๋ฉฐ, ํ”„๋กœ๊ทธ๋žจ ์˜ค๋ฅ˜๋Š” ๊ฐ€๋Šฅํ•œ ์ดˆ๋ฐ˜์— ๋ฐœ๊ฒฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

ํ•˜์ง€๋งŒ ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๋‹ค ์“ฐ์ž๋งˆ์ž null ์ฒ˜๋ฆฌ๋ฅผ ํ•  ํ•„์š”๋Š” ์—†๊ณ , ์ด๋Š” ํ•„์š” ์ด์ƒ์œผ๋กœ ํ”„๋กœ๊ทธ๋žจ์„ ์ง€์ €๋ถ„ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ null ์ฒ˜๋ฆฌํ•˜๋Š” ์ผ์€ ์˜ˆ์™ธ์ ์ธ ๊ฒฝ์šฐ์—ฌ์•ผ ํ•œ๋‹ค. ๋‹ค์“ด ์ฐธ์กฐ๋ฅผ ํ•ด์ œํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ๊ทธ ์ฐธ์กฐ๋ฅผ ๋‹ด์€ ๋ณ€์ˆ˜๋ฅผ ์œ ํšจ ๋ฒ”์œ„(scope) ๋ฐ–์œผ๋กœ ๋ฐ€์–ด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค.

์œ„ Stack class๋Š” ์ž๊ธฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์— ์ทจ์•ฝํ•˜๋‹ค. ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ๋‹ด๋Š” elements ๋ฐฐ์—ด๋กœ ์ €์žฅ์†Œ ํ’€์„ ๋งŒ๋“ค์–ด ์›์†Œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”๋ฐ, GC๋Š” ํ•ด๋‹น ๋ฐฐ์—ด์—์„œ ๋น„ํ™œ์„ฑํ™” ์˜์—ญ์— ์ฐธ์กฐํ•˜๋Š” ๊ฐ์ฒด๋ฅผ ๋˜‘๊ฐ™์ด ์œ ์š”ํ•œ ๊ฐ์ฒด๋กœ ๋ณด๊ธฐ ๋•Œ๋ฌธ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋น„ํ™œ์„ฑ ์˜์—ญ์ด ๋˜๋Š” ์ˆœ๊ฐ„ null ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๋” ์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๊ฒƒ์„ GC์— ์•Œ๋ ค์•ผํ•œ๋‹ค. ์ด๋Š” ๋‹จ์ˆœํžˆ ์œ„ stack class๋งŒ์„ ๋งํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฉฐ, Collection ํด๋ž˜์Šค๋Š” ๋ชจ๋‘ ์ฃผ์˜ํ•ด์•ผํ•œ๋‹ค.

์บ์‹œ ๋˜ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์ผ์œผํ‚ค๋Š” ์š”์†Œ์ด๋‹ค. ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ์บ์‹œ์— ๋„ฃ์–ด๋‘๊ณ , ์ด ์‚ฌ์‹ค์„ ์žŠ์€ ์ฑ„ ๊ทธ ๊ฐ์ฒด๋ฅผ ๊ณ„์†ํ•ด์„œ ๋†”๋‘๋Š” ๊ฒฝ์šฐ๋ฅผ ํ”ํžˆ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€์ด๋‹ค.

  1. WeakHashMap : ์บ์‹œ ์™ธ๋ถ€์—์„œ ํ‚ค๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋™์•ˆ๋งŒ ์—”ํŠธ๋ฆฌ๊ฐ€ ์‚ด์•„ ์žˆ๋Š” ์บ์‹œ๊ฐ€ ํ•„์š”ํ•œ ์ƒํ™ฉ

  2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ(ScheduledThreadPoolExecutor)๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜, ์บ์‹œ์— ์ƒˆ ์—”ํŠธ๋ฆฌ ์ถ”๊ฐ€์‹œ ๋ถ€์ˆ˜ ์ž‘์—…์œผ๋กœ ์“ฐ์ง€ ์•Š๋Š” ์—”ํŠธ๋ฆฌ๋ฅผ ์ฒญ์†Œํ•˜๋Š” ๋ฐฉ๋ฒ•

    // LinkedHashMap์€ ๋’ค์˜ ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์—”ํŠธ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌ
    void afterNodeInsertion(boolean evict) { // possibly remove eldest
     LinkedHashMap.Entry<K,V> first;
     if (evict && (first = head) != null && removeEldestEntry(first)) {
       K key = first.key;
       removeNode(hash(key), key, null, false, true);
     }
    }

๋ฆฌ์Šค๋„ˆ ํ˜น์€ ์ฝœ๋ฐฑ ๋˜ํ•œ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์˜ ์š”์†Œ์ด๋‹ค. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฝœ๋ฐฑ๋งŒ ๋“ฑ๋กํ•˜๊ณ  ๋ช…ํ™•ํžˆ ํ•ด์ง€ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์ฝœ๋ฐฑ์€ ๊ณ„์† ์Œ“์—ฌ๋งŒ ๊ฐˆ ๊ฒƒ์ด๋‹ค. ์ด๋Ÿด ๋•Œ ์ฝœ๋ฐฑ์„ ์•ฝํ•œ ์ฐธ์กฐ๋กœ ์ €์žฅํ•˜๋ฉด, GC๊ฐ€ ์ฆ‰์‹œ ์ˆ˜๊ฑฐํ•ด๊ฐ„๋‹ค.(ex) WeakHashMap ์˜ ํ‚ค๋กœ ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•

WeakHashMap

List, Map, Set ๊ฐ™์€ ์ž๋ฐ” Collection ํด๋ž˜์Šค๋“ค์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํ•ญ์ƒ ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค. Collection ํด๋ž˜์Šค ์•ˆ์— ๋‹ด๊ฒจ์žˆ๋Š” ์ธ์Šคํ„ด์Šค๋Š” ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉ์—ฌ๋ถ€์™€ ๊ด€๊ณ„ ์—†์ด ๋ชจ๋‘ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์œผ๋กœ ํŒ๋‹จ๋˜์–ด GC์˜ ๋Œ€์ƒ์ด ๋˜์ง€ ์•Š์•„ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์˜ ํ”ํ•œ ์›์ธ์ด ๋œ๋‹ค.

์ผ๋ฐ˜์ ์ธ HashMap์˜ ๊ฒฝ์šฐ Map์•ˆ์— Key/Value๊ฐ€ ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋ฉด ์‚ฌ์šฉ์—ฌ๋ถ€์™€ ๊ด€๊ณ„ ์—†์ด ํ•ด๋‹น ์ฐธ์กฐ๋Š” ์ง€์›Œ์ง€์ง€ ์•Š๋Š”๋‹ค. Key์— ํ•ด๋‹นํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ ๋” ์ด์ƒ ์กด์žฌํ•˜์ง€ ์•Š๊ฒŒ๋˜์–ด null ์ด ๋˜์—ˆ์„ ๊ฒฝ์šฐ HashMap ์—์„œ๋„ ๋” ์ด์ƒ ๊บผ๋‚ผ ์ผ์ด ์—†๋Š” ๊ฒฝ์šฐ๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณด์ž. HashMap์˜ ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐ์ฒด๊ฐ€ ์‚ฌ๋ผ์ง€๋”๋ผ๋„ GC๋Œ€์ƒ์œผ๋กœ ์žก์ง€ ๋ชปํ•˜์—ฌ ์ปฌ๋ ‰์…˜์— ์Œ“์—ฌ, ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜์˜ ์›์ธ์ด ๋œ๋‹ค.

์ด๋•Œ WeakHashMap์€WeakReference๋ฅผ ์ด์šฉํ•˜์—ฌ HashMap์˜ Key๋ฅผ ๊ตฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค.WeakHashMap์— ์žˆ๋Š” Key๊ฐ’์ด ๋”์ด์ƒ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค๊ณ  ํŒ๋‹จ๋˜๋ฉด ๋‹ค์Œ GC๋•Œ ํ•ด๋‹น Key, Value ์Œ์„ ์ œ๊ฑฐํ•œ๋‹ค. ์ž„์˜๋กœ ์ œ๊ฑฐ๋˜์–ด๋„ ์ƒ๊ด€์—†๋Š” ๋ฐ์ดํ„ฐ๋“ค์„ ์œ„ํ•ด ์ฃผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

 /**
     * The entries in this hash table extend WeakReference, using its main ref
     * field as the key.
     */
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

        @SuppressWarnings("unchecked")
        public K getKey() {
            return (K) WeakHashMap.unmaskNull(get());
        }

        public V getValue() {
            return value;
        }

        public V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
public class ReferenceTest {

    public static void main(String[] args){

        HashMap<Integer, String> hashMap = new HashMap<>();

        Integer key1 = 1000;
        Integer key2 = 2000;
        Integer key3 = 3000;
        hashMap.put(key3, "test c");
        hashMap.put(key2, "test b");

        key3 = null;

        System.out.println("HashMap GC ์ˆ˜ํ–‰ ์ด์ „");
        hashMap.entrySet().stream().forEach(el -> System.out.println(el));

        System.gc();

        System.out.println("HashMap GC ์ˆ˜ํ–‰ ์ดํ›„");
        hashMap.entrySet().stream().forEach(el -> System.out.println(el));

        WeakHashMap<Integer, String> map = new WeakHashMap<>();

        map.put(key1, "test a");
        map.put(key2, "test b");

        key1 = null;

        System.out.println("WeakHashMap GC ์ˆ˜ํ–‰ ์ด์ „");
        map.entrySet().stream().forEach(el -> System.out.println(el));

        System.gc();

        System.out.println("WeakHashMap GC ์ˆ˜ํ–‰ ์ดํ›„");
        map.entrySet().stream().forEach(el -> System.out.println(el));

    }
}
HashMap GC ์ˆ˜ํ–‰ ์ด์ „
2000=test b
3000=test c
HashMap GC ์ˆ˜ํ–‰ ์ดํ›„
2000=test b
3000=test c
WeakHashMap GC ์ˆ˜ํ–‰ ์ด์ „
1000=test a
2000=test b
WeakHashMap GC ์ˆ˜ํ–‰ ์ดํ›„
2000=test b

WeakHashMap์˜ Value๋Š” ๊ฐ•ํ•œ ์ฐธ์กฐ์— ์˜ํ•ด ๋ณด๊ด€ ์œ ์ง€๋œ๋‹ค. Value ๊ฐ์ฒด๊ฐ€ ์ง๊ฐ„์ ‘์ ์œผ๋กœ ์ž์‹ ์˜ Key๋ฅผ ๊ฐ•ํ•œ์ฐธ์กฐํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ์—๋Š” Key๊ฐ€ ์‚ญ์ œ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋งŒ์•ฝ Key๋ฅผ ์ฐธ์กฐํ•˜๋Š” Value๋ฅผ ์‚ฌ์šฉํ•ด WeakHashMap ๋˜ํ•œ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ธธ ๋ฐ”๋ž€๋‹ค๋ฉด WeakReference๋กœ ๋ž˜ํ•‘ํ•ด์ฃผ๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

weakHashMap.put(key, new WeakReference(value));

GC ํ™•์ธํ•˜๊ธฐ

๋งŒ์•ฝ GC๊ฐ€ ์ˆ˜ํ–‰๋˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜ ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

Intellj์˜ Edit Configurations -> VM options์— -verbose:gc -XX:+PrintCommandLineFlags๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ํ˜„์žฌ GC๊ฐ€ ์ˆ˜ํ–‰์ค‘์ธ์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

public static void main(String[] args) throws InterruptedException {
  List<Integer> li = IntStream.range(1, 100).boxed().collect(Collectors.toList());

  for (int i=1; true; i++) {
    if (i % 100 == 0) {
      li = new ArrayList<>();
      Thread.sleep(100);
    }
    IntStream.range(0, 100).forEach(li::add);
  }
}
[GC (Allocation Failure)  33246K->1292K(125952K), 0.0035248 secs]
[GC (Allocation Failure)  34572K->1284K(125952K), 0.0028950 secs]
[GC (Allocation Failure)  34564K->1200K(125952K), 0.0033107 secs]
[GC (Allocation Failure)  34480K->1268K(125952K), 0.0045468 secs]
...

์ฐธ๊ณ 

Last updated