자바 서버개발자보다는 자바 임베디드 개발자 내부 캐쉬 용도 용도로 쓰기 위해서 java.lang.ref.Reference클래스를 사용하기도 한다. 잘만 쓰면 메모릭 릭을 잘 방지하면서 사용할 수 있다.

 

* SoftReference(SR)

풍부하게 메모리를 넉넉히 쓰지만, 최초의 GC가 돌아간 후, JVM은 메모리 공간이 없으면 SoftRefernece 인스턴스를 gc 대상으로 잡는다. OOME 가 발생할 수 있는 상황에서 softly-reachable objects가 있다면, 이 objects를 모두 메모리에서 정리한다. 언제 gc될지는 모른다.

안드로이드 API(http://developer.android.com)에 따르면, as late as possible이다.

 

실제 구현 예)  Tomcat의 jdbc 쪽 SoftReferenceObjectPool.java을 참조하면 좋음

간단하게 new SoftReference(객체인스턴스)로 해서 만든다.

http://www.docjar.com/html/api/org/apache/commons/pool/impl/SoftReferenceObjectPool.java.html

package org.apache.commons.pool.impl;

import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.Reference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import org.apache.commons.pool.BaseObjectPool;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.PoolUtils;


public class SoftReferenceObjectPool extends BaseObjectPool implements ObjectPool {
….



public synchronized void addObject() throws Exception {
        assertOpen();
        if (_factory == null) {
            throw new IllegalStateException("Cannot add objects without a factory.");
        }
        Object obj = _factory.makeObject();

        boolean success = true;
        if(!_factory.validateObject(obj)) {
            success = false;
        } else {
            _factory.passivateObject(obj);
        }

        boolean shouldDestroy = !success;
        if(success) {
           _pool.add(new SoftReference(obj, refQueue));
            notifyAll(); // _numActive has changed
        }

        if(shouldDestroy) {
            try {
                _factory.destroyObject(obj);
            } catch(Exception e) {
                // ignored
            }
        }
    }



* WeakReference(WR) , WeakHashMap

객체가 사용 중이기는 하지만, 더 이상 사용하지 않을 것 같으면 컨테이너에서 삭제한다.

쉽게 말해서 일종의 잠깐 동안의 캐쉬로서 gc가 실행되면 메모리에서 정리한다.

아래 코드를 실행하면, 설명은 그다지 필요하지 않다.


import java.lang.ref.WeakReference;

public class ReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        Student s1 = new Student(1);
        System.out.println("1: " + s1);
        WeakReference<Student> ws = new WeakReference<Student>(s1);
        System.out.println("2: " + ws.get());
        s1 = null;
        System.gc();
        Thread.sleep(1000);
        System.out.println("3: " + ws.get());
    }
}

class Student {
    int id;
    public Student(int id) {
        this.id = id;
    }
    public String toString() {
        return "[id=" + id + "]";
    }
}

결과는 다음과 같다. gc이후에 WeakReference 객체는 모두 메모리에서 정리된다. 

1: [id=1]
2: [id=1]
3: null

 

톰캣 컨테이너에서 사용하고 있는 ConcurrentCache.java를 예로 든다.

package org.apache.el.util;

import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

public final class ConcurrentCache<K,V> {

    private final int size;

    private final Map<K,V> eden;

    private final Map<K,V> longterm;

    public ConcurrentCache(int size) {
        this.size = size;
        this.eden = new ConcurrentHashMap<K,V>(size);
        this.longterm = new WeakHashMap<K,V>(size);
    }

    public V get(K k) {
        V v = this.eden.get(k);
        if (v == null) {
            synchronized (longterm) {
                v = this.longterm.get(k);
            }
            if (v != null) {
                this.eden.put(k, v);
            }
        }
        return v;
    }

    public void put(K k, V v) {
        if (this.eden.size() >= size) {
            synchronized (longterm) {
                this.longterm.putAll(this.eden);
            }
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
}

java의 ThreadLocal.ThreadLocalMap에서도 WeakReference를 사용하고 있다.

static class ThreadLocalMap {

 
       static class Entry extends WeakReference<ThreadLocal> {
           /** The value associated with this ThreadLocal. */
           Object value;

           Entry(ThreadLocal k, Object v) {
               super(k);
               value = v;
           }
       }

       private static final int INITIAL_CAPACITY = 16;

 
       private Entry[] table;

    
       private int size = 0;

    
       private int threshold; // Default to 0


       private void setThreshold(int len) {
           threshold = len * 2 / 3;
       }

     
       private static int nextIndex(int i, int len) {
           return ((i + 1 < len) ? i + 1 : 0);
       }

 
       private static int prevIndex(int i, int len) {
           return ((i - 1 >= 0) ? i - 1 : len - 1);
       }

     
       ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
           table = new Entry[INITIAL_CAPACITY];
           int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
           table[i] = new Entry(firstKey, firstValue);
           size = 1;
           setThreshold(INITIAL_CAPACITY);
       }

   
       private ThreadLocalMap(ThreadLocalMap parentMap) {
           Entry[] parentTable = parentMap.table;
           int len = parentTable.length;
           setThreshold(len);
           table = new Entry[len];

           for (int j = 0; j < len; j++) {
               Entry e = parentTable[j];
               if (e != null) {
                   ThreadLocal key = e.get();
                   if (key != null) {
                       Object value = key.childValue(e.value);
                       Entry c = new Entry(key, value);
                       int h = key.threadLocalHashCode & (len - 1);
                       while (table[h] != null)
                           h = nextIndex(h, len);
                       table[h] = c;
                       size++;
                   }
               }
           }
       }

   …..

 

안드로이드 API(http://developer.android.com)에서는 구현체에 대해서 더욱 세밀하게 설명이 들어가 있다.

Implements a weak reference, which is the middle of the three types of references. Once the garbage collector decides that an object obj is is weakly-reachable, the following happens:

  • A set ref of references is determined. ref contains the following elements:
    • All weak references pointing to obj.
    • All weak references pointing to objects from which obj is either strongly or softly reachable.
  • All references in ref are atomically cleared.
  • All objects formerly being referenced by ref become eligible for finalization.
  • At some future point, all references in ref will be enqueued with their corresponding reference queues, if any.

 

 

* PhantomReference(PR)

soft, weak보다 엄청 약하다. gc가 돌기 전(즉, 이것은 gc 대상이야 라고 결정할 때, finalize() 호출 후 ) 메모리에서 정리된다. 아무래도 gc 되기 전에 cleanup 해야 할 때 사용한다. 내부적으로는 유지하고 있지만, 객체를 다시 꺼내오면 null이 된다.

아주 특수한 경우에 쓰인다. sun.misc.Cleaner  클래스가 PhantomReference를 사용하고 있다.

http://knight76.tistory.com/1548 

재미있는 블로그(http://weblogs.java.net/blog/kcpeppe/archive/2011/09/29/mysterious-phantom-reference)가 있는데, PhantomReference 썼다가 이상하게 (미스테리하게) 나와서, 조금 수정해서 해결했다는 내용이다.

package snippet;

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;

public class ReferenceTest {
    public static void main(String[] args) throws InterruptedException {
        test1();
        test2();
    }
   
    public static void test1() {
        ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();
        ArrayList<PhantomReference<Foo>> list = new ArrayList<PhantomReference<Foo>>();

        for (int i = 0; i < 10; i++) {
            Foo o = new Foo(Integer.toOctalString(i));
            list.add(new PhantomReference<Foo>(o, queue));
        }

        // make sure the garbage collector does it’s magic
        System.gc();

        // lets see what we’ve got
        Reference<? extends Foo> referenceFromQueue;

        for (PhantomReference<Foo> reference : list) {
            System.out.println("x: " + reference.isEnqueued());
        }
        while ((referenceFromQueue = queue.poll()) != null) {
            System.out.println("y: " + referenceFromQueue.get());
            referenceFromQueue.clear();
        }
    }

    public static void test2() {
        // initialize
        ReferenceQueue<Foo> queue = new ReferenceQueue<Foo>();
        ArrayList< FinalizeStuff<Foo>> list = new ArrayList<FinalizeStuff<Foo>>();
        ArrayList<Foo> foobar = new ArrayList<Foo>();
        
        for ( int i = 0; i < 10; i++) {
            Foo o = new Foo( Integer.toOctalString( i));
            foobar.add(o);
            list.add(new FinalizeStuff<Foo>(o, queue));
        }
        
        // release all references to Foo and make sure the garbage collector does it’s magic
        foobar = null;
        System.gc();
        
        // should be enqueued
        Reference<? extends Foo> referenceFromQueue;
        for ( PhantomReference<Foo> reference : list) {
            System.out.println(reference.isEnqueued());
        }
        
        // now we can call bar to do what ever it is we need done
        while ( (referenceFromQueue = queue.poll()) != null) {
            ((FinalizeStuff)referenceFromQueue).bar();
            referenceFromQueue.clear();
        }
    }
}


class Foo {
    private String bar;

    public Foo(String bar) {
        this.bar = bar;
    }

    public String get() {
        return bar;
    }
}


class FinalizeStuff<Foo> extends PhantomReference<Foo> {
    public FinalizeStuff(Foo foo, ReferenceQueue<? super Foo> queue) {
        super(foo, queue);
    }
 
    public void bar() {
        System.out.println("foobar is finalizing resources");
    }
}

테스트 결과는 다음과 같다.

x: true
x: true
x: true
x: true
x: true
x: true
x: true
x: true
x: true
x: true
y: null
y: null
y: null
y: null
y: null
y: null
y: null
y: null
y: null
y: null
true
true
true
true
true
true
true
true
true
true
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources
foobar is finalizing resources

Posted by 김용환 '김용환'

댓글을 달아 주세요