๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

reviews/Effective JAVA

007. ๋‹ค ์“ด ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ํ•ด์ œํ•˜๋ผ

๐Ÿ‘‰ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

์ปดํ“จํ„ฐ ํ”„๋กœ๊ทธ๋žจ์ด ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ณ„์† ์ ์œ ํ•˜๊ณ  ์žˆ๋Š” ํ˜„์ƒ์„ ๋งํ•œ๋‹ค.

♦๏ธŽ ๊ฐ์ฒด๋“ค์˜ ๋‹ค ์“ด ์ฐธ์กฐ

๋ฐฐ์—ด๋กœ Stack ์„ ๊ตฌํ˜„

/** StackMemoryLeak.java */
public class StackMemoryLeak {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public StackMemoryLeak() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

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

    public Object pop() {
        if (size == 0) {
            throw new EmptyStackException();
        }

        Object result = elements[size - 1];
        size--;

        return result;
    }

    /**
     * ์›์†Œ๋ฅผ ์œ„ํ•œ ๊ณต๊ฐ„์„ ์ ์–ด๋„ ํ•˜๋‚˜ ์ด์ƒ ํ™•๋ณดํ•œ๋‹ค.
     * ๋ฐฐ์—ด ํฌ๊ธฐ๋ฅผ ๋Š˜๋ ค์•ผ ํ•  ๋•Œ๋งˆ๋‹ค ๋Œ€๋žต ๋‘ ๋ฐฐ์”ฉ ๋Š˜๋ฆฐ๋‹ค. */
    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}

์ด ์ฝ”๋“œ์—์„œ๋Š” ์Šคํƒ์ด ์ปค์กŒ๋‹ค๊ฐ€ ์ค„์–ด๋“ค์—ˆ์„ ๋•Œ ์Šคํƒ์—์„œ ๊บผ๋‚ด์ง„ ๊ฐ์ฒด๋“ค์„ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ํšŒ์ˆ˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์ด ์Šคํƒ์„ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ์˜ค๋ž˜ ์‹คํ–‰ํ•˜๋‹ค ๋ณด๋ฉด ์ ์ฐจ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๊ฐ€ ์ผ์–ด๋‚˜ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ํ™œ๋™๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ๋Š˜์–ด๋‚˜ ๊ฒฐ๊ตญ ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ๊ฒƒ์ด๋‹ค.

์‹ฌํ•œ ๊ฒฝ์šฐ ๋””์Šคํฌ ํŽ˜์ด์ง•*์ด๋‚˜ OutOfMemoryError๋ฅผ ์ผ์œผ์ผœ ํ”„๋กœ๊ทธ๋žจ์ด ์˜ˆ๊ธฐ์น˜ ์•Š๊ฒŒ ์ข…๋ฃŒ๋  ์ˆ˜ ์žˆ๋‹ค.

์œ„ Stack ์ฝ”๋“œ์—์„œ elements ๋ฐฐ์—ด์˜ ์ธ๋ฑ์Šค๊ฐ€ size ์ด์ƒ์ธ ์š”์†Œ๋“ค์€ Stack์—์„œ ๋น„ํ™œ์„ฑํ™”๋œ ๊ฐ์ฒด์ง€๋งŒ elements์— ๊ทธ๋Œ€๋กœ ๋‚จ์•„ ๊ฐ์ฒด๋“ค์˜ ๋‹ค ์“ด ์ฐธ์กฐ(obsolete reference)๋ฅผ ์—ฌ์ „ํžˆ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์žก๊ธฐ

๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ๋ณด๊ธฐ์—๋Š” ๋น„ํ™œ์„ฑ ์˜์—ญ์—์„œ ์ฐธ์กฐํ•˜๋Š” ๊ฐ์ฒด๋„ ๋˜‘๊ฐ™์ด ์œ ํšจํ•œ ๊ฐ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ๋” ์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๊ฒƒ์„ ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์ง์ ‘ ์ฐธ์กฐ ํ•ด์ œํ•˜์—ฌ ์•Œ๋ ค์•ผ ํ•œ๋‹ค.

/** StackMemoryLeak.java */
public Object pop() {
    if (size == 0) {
        throw new EmptyStackException();
    }

    Object result = elements[size - 1];
    elements[size - 1] = null; // ํ•ด๋‹น ์ฐธ์กฐ๋ฅผ ๋‹ค ์ผ์œผ๋ฏ€๋กœ null ์ฒ˜๋ฆฌ ํ•˜์—ฌ ์ฐธ์กฐ ํ•ด์ œํ•จ
    size--;

    return result;
}

์œ„์ฒ˜๋Ÿผ ๊ฐ ์›์†Œ์˜ ์ฐธ์กฐ๊ฐ€ ๋” ์ด์ƒ ํ•„์š” ์—†์–ด์ง€๋Š” ์‹œ์ . ์ฆ‰, ์Šคํƒ์—์„œ ๊บผ๋‚ด์งˆ ๋•Œ ์ฐธ์กฐ ํ•ด์ œํ•œ๋‹ค.

(์ฐธ๊ณ ) Stack

/** java.util.Stack.java */
public synchronized E pop() {
    E       obj;
    int     len = size();

    obj = peek();
    removeElementAt(len - 1);

    return obj;
}

/** java.util.Vector.java */
public synchronized void removeElementAt(int index) {
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
    }
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    int j = elementCount - index - 1;
    if (j > 0) { // ์ค‘๊ฐ„ index ๊ฐ’์ด ์‚ญ์ œ๋œ ๊ฒฝ์šฐ index ๋’ค ๊ฐ’๋“ค์„ ํ•œ์นธ์”ฉ ๋•ก๊น€
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }
    modCount++;
    elementCount--;
    elementData[elementCount] = null; /* to let gc do its work */
}

๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ null ์ฒ˜๋ฆฌ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ null ์ฒ˜๋ฆฌํ•˜๋Š” ์ผ์€ ์˜ˆ์™ธ์ ์ธ ๊ฒฝ์šฐ์—ฌ์•ผ ํ•œ๋‹ค.

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

♦๏ธŽ ์บ์‹œ

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

์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์บ์‹œ์— ์ €์žฅ๋œ ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ํ•ด์ง€ํ•˜๊ธฐ ์œ„ํ•œ ํ•ด๋ฒ•์€ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ๊ฐ€์ง€์ด๋‹ค.

(์ฐธ๊ณ ) ์ž๋ฐ”์˜ ์„ธ๊ฐ€์ง€ ์œ ํ˜•์˜ ์ฐธ์กฐ*

WeakHashMap

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

/** ReferenceTest.java */
public static void main(String[] args) {
    HashMap<Integer, String> hashMap = new HashMap<>();
    WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<>();

    Integer key1 = 1000;
    Integer key2 = 2000;
    Integer wKey1 = 1000;
    Integer wKey2 = 2000;

    hashMap.put(key1, "test a");
    hashMap.put(key2, "test b");
    weakHashMap.put(wKey1, "test a");
    weakHashMap.put(wKey2, "test b");

    key1 = null;
    wKey1 = null;

    System.gc();  //๊ฐ•์ œ Garbage Collection

    hashMap.entrySet().stream().forEach(el -> System.out.println(el));
    System.out.println("--------");
    weakHashMap.entrySet().stream().forEach(el -> System.out.println(el));
}

2000=test b
1000=test a
--------
2000=test b
  • equals ๋ฉ”์†Œ๋“œ๊ฐ€ == ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” Key๋ฅผ ์“ด ๊ฒฝ์šฐ Key๊ฐ€ ๋ฒ„๋ ค์ง€๋ฉด ๋™์ผํ•œ Key๊ฐ€ ์ƒ์„ฑ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋” ์œ ์šฉํ•˜๋‹ค. (Key๊ฐ€ String๊ณผ ๊ฐ™์ด ๋‹จ์ผ ๊ฐ’์„ ๊ฐ€์ง€๋Š” Class๊ณ  == ์—ฐ์‚ฐ์ž ์™ธ์— equals๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„œ๋กœ ๋‹ค๋ฅธ ๊ฐ์ฒด๋ผ๋„ ๋™์ผ์„ฑ์„ ์ฒดํฌํ•˜์—ฌ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ ์ ์ ˆํ•˜์ง€ ์•Š๋‹ค.)
  • key๋กœ ์“ฐ๋Š” String์„ ๋ฆฌํ„ฐ๋Ÿด ๋ฐฉ์‹์œผ๋กœ ์ƒ์„ฑํ•œ ๊ฒฝ์šฐ String interning ๋•Œ๋ฌธ์— String pool์—์„œ ๊ด€๋ฆฌ๋˜๊ธฐ ๋•Œ๋ฌธ์— null์ด ๋œ๋‹ค๊ณ  ํ•ด๋„ WeakHashMap์ด ์‚ญ์ œ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • Value ๊ฐ์ฒด๊ฐ€ ์ง์ ‘ ๋˜๋Š” ๊ฐ„์ ‘์ ์œผ๋กœ ์ž์‹ ์˜ key๋ฅผ ๊ฐ•ํ•œ ์ฐธ์กฐ ํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค. ๋งŒ์•ฝ key๋ฅผ ์ฐธ์กฐํ•˜๋Š” value๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ , WeakHashMap๋„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋™์ž‘ํ•˜๊ธธ ์›ํ•œ๋‹ค๋ฉด WeakReferences ๋‚ด์—์„œ value๋ฅผ ๋‹ค์‹œ ๋ž˜ํ•‘ํ•˜๋Š” ๋ฐฉ์‹์„ ์จ์•ผ ํ•œ๋‹ค. ex) m.put(key, new WeakReference(value);

LinkedHashMap

์บ์‹œ ์—”ํŠธ๋ฆฌ์˜ ์œ ํšจ ๊ธฐ๊ฐ„์„ ์ •ํ™•ํžˆ ์ •์˜ํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ์—”ํŠธ๋ฆฌ์˜ ๊ฐ€์น˜๋ฅผ ๋–จ์–ด๋œจ๋ฆฌ๋Š” ๋ฐฉ์‹์„ ํ”ํžˆ ํ•˜์šฉํ•˜๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ ์“ฐ์ง€ ์•Š๋Š” ์—”ํŠธ๋ฆฌ๋ฅผ ์ด๋”ฐ๊ธˆ ์ฒญ์†Œํ•ด์ค˜์•ผ ํ•œ๋‹ค.

removeEldestEntry ๋ฉ”์„œ๋“œ๋ฅผ ์จ์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ ์บ์‹œ์— ์ƒˆ ์—”ํŠธ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๋ถ€์ˆ˜ ์ž‘์—…์œผ๋กœ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ํ•œ๋‹ค.

♦๏ธŽ ๋ฆฌ์Šค๋„ˆ(listener) ํ˜น์€ ์ฝœ๋ฐฑ(callback)

ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฝœ๋ฐฑ์„ ๋“ฑ๋ก๋งŒ ํ•˜๊ณ  ๋ช…ํ™•ํžˆ ํ•ด์ง€ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ฝœ๋ฐฑ์€ ๊ณ„์† ์Œ“์ธ๋‹ค.

์ฝœ๋ฐฑ์„ ์•ฝํ•œ ์ฐธ์กฐ(weak reference)๋กœ ์ €์žฅํ•˜๋ฉด ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ์ฆ‰์‹œ ์ˆ˜๊ฑฐํ•œ๋‹ค.

ex) WeakHashMap์— ํ‚ค๋กœ ์ €์žฅํ•˜๊ธฐ


*ํŽ˜์ด์ง• : ๋ฌผ๋ฆฌ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ณ  ์žˆ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์˜์—ญ์ด ํ•˜๋“œ ๋“œ๋ผ์ด๋ธŒ์— ์ผ์‹œ์ ์œผ๋กœ ์ €์žฅ๋˜๋Š” ๊ฒƒ

*์ž๋ฐ”์˜ ์„ธ ๊ฐ€์ง€ ์œ ํ˜•์˜ ์ฐธ์กฐ

- ๊ฐ•ํ•œ ์ฐธ์กฐ(Strong reference)

Integer strong = 1;

๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ ์ฐธ์กฐ ์œ ํ˜•

num ๋ณ€์ˆ˜๋Š” ๊ฐ’์ด 1์ธ Integer ๊ฐ์ฒด์— ๋Œ€ํ•ด ๊ฐ•ํ•œ ์ฐธ์กฐ๋ฅผ ๊ฐ€์ง„๋‹ค.

๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ๊ฐ•ํ•œ ์ฐธ์กฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๊ทธ ๊ฐ์ฒด๋Š” ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์˜ ๋Œ€์ƒ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.

- ๋ถ€๋“œ๋Ÿฌ์šด ์ฐธ์กฐ(Soft reference)

SoftReference<Integer> soft = new SoftReference<Integer>(strong);

๋งŒ์•ฝ strong == null์ด ๋˜๋ฉด ๋” ์ด์ƒ ์›๋ณธ(storng)์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ soft ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ SoftReference๋งŒ ์กด์žฌํ•  ๊ฒฝ์šฐ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๋Œ€์ƒ์ด ๋œ๋‹ค.

๋‹จ, ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•˜์ง€ ์•Š์œผ๋ฉด ๊ตณ์ด ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ํ•˜์ง€ ์•Š๋Š”๋‹ค.

์—„๊ฒฉํ•˜์ง€ ์•Š์€ ์บ์‹œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋“ค์—์„œ ์‚ฌ์šฉ๋œ๋‹ค.

- ์•ฝํ•œ ์ฐธ์กฐ(Weak reference)

WeakReference<Integer> weak = new WeakReference<Integer>(strong);

๋งŒ์•ฝ strong == null์ด ๋˜๊ณ  weak ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฐธ์กฐ๊ฐ€ WeakReference ๋ฟ์ผ ๊ฒฝ์šฐ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜ ๋Œ€์ƒ์ด ๋œ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•˜์ง€ ์•Š๋”๋ผ๋„ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰์…˜์˜ ๋Œ€์ƒ์ด ๋˜์–ด ๋‹ค์Œ ๊ฐ€๋น„์ง€ ์ปฌ๋ ‰ํ„ฐ๊ฐ€ ๋™์ž‘ํ•˜๋Š” ์‹œ์ ์— ๋ฌด์กฐ๊ฑด ์—†์–ด์ง„๋‹ค.


์ฐธ๊ณ ํ•œ ๊ณณ ์ถœ์ฒ˜

https://blog.breakingthat.com/2018/08/26/java-collection-map-weakhashmap/

๋ฐ˜์‘ํ˜•