์๋ฐ์์๋ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๋์์ค๋ค. ํ์ง๋ง ๊ฐ๋น์ง, ์ปฌ๋ ํฐ๊ฐ ์๋ค๊ณ ํด๋ ๋ฉ๋ชจ๋ฆฌ ๋์๊ฐ ๋ฐ์ํ์ง ์๋ ๊ฒ์ ์๋๋ค. ์ด๋ฒ ๊ธ์์๋ ๋ค ์ด ๊ฐ์ฒด ์ฐธ์กฐ๋ฅผ ํด์ ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํ ์ ์๋ 3๊ฐ์ง ์ํฉ์ ์์๋ณด๊ณ ์ ํ๋ค.
1. Stack์์์ ๋ฉ๋ชจ๋ฆฌ ๋์ & ํด๊ฒฐ์ฑ
public class Stack{
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
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);
}
}
ํด๋น ์ฝ๋์์ ๋ฉ๋ชจ๋ฆฌ ๋์๋ ์ด๋์์ ์ผ์ด๋ ๊น? ์ด ์ฝ๋์์๋ ์คํ์ด ์ปค์ก๋ค๊ฐ ์ค์ด๋ค์์ ๋ ์คํ์์ ๊บผ๋ด์ง ๊ฐ์ฒด๋ค์ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ํ์ํ์ง ์๋๋ค. ํ๋ก๊ทธ๋จ์์ ๊ทธ ๊ฐ์ฒด๋ค์ ๋ ์ด์ ์ฌ์ฉํ์ง ์๋๋ผ๋ ํด๋น ์คํ์ด ๊ทธ ๊ฐ์ฒด๋ค์ ๋ค ์ด ์ฐธ์กฐ(obsolete reference)๋ฅผ ์ฌ์ ํ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ๊ฐ์ฒด๋ค์ ํ์ํ์ง ์๋๋ค. (๋ค ์ด ์ฐธ์กฐ = ์์ผ๋ก ๋ค์ ์ฐ์ง ์์ ์ฐธ์กฐ)
๊ทธ๋์ ์๋์ ๊ฐ์ด ํด๋น ์ฐธ์กฐ๋ฅผ ๋ค ์ผ์ ๋ null ์ฒ๋ฆฌ(์ฐธ์กฐ ํด์ )ํ์ฌ ํด๋น ๊ฐ์ฒด๋ฅผ ๋๋ ์ฐ์ง ์์ ๊ฒ์์ ๊ฐ๋น์ง ์ปค๋ ํฐ์ ์๋ ค์ผ ํ๋ค.
public Object pop(){
if(size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // ๋ค ์ด ์ฐธ์กฐ ํด์
return result;
}
2. ์บ์์์์ ๋ฉ๋ชจ๋ฆฌ ๋์ & ํด๊ฒฐ์ฑ
1. ๋ฐฉ๋ฒ 1
public class PostRepository{
private Map<CacheKey, Post> cache;
public PostRepository(){
this.cache = new HashMap<>();
}
public Post getPostById(Integer id){
CacheKey key = new CacheKey(id);
if(cache.containKey(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();
Integer p1 = 1;
postRepository.getPostById(p1);
assertFalse(postRepository.getCache().isEmpty());
p1 = null;
// TODO run gc
System.out.println("run gc");
System.gc(); // ํญ์ ์ด ์์ ์ gc๊ฐ ์คํ๋๋ค๋ ๋ณด์ฅ์ ์์ง๋ง, ์คํ์ด ๋๊ธฐ๋ ํ๋ค.
System.out.println("wait");
Thread.sleep(3000L);
assertTrue(postRepository.getCache().isEmpty()); // AssertionFailedError
}
}
ํด๋น ์ฝ๋๋ฅผ ์คํ์์ผ๋ณด๋ฉด gc๊ฐ ์คํ๋๋๋ผ๋ postRepository๊ฐ ๋น์ด์์ง ์๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
public class PostRepository{
...
public PostRepository(){
this.cache = new WeakHashMap<>();
}
...
}
๊ธฐ์กด์ HashMap์์ WeakHashMap์ผ๋ก ๋ฐ๊พธ์๋ค. ๊ทธ๋ฌ๋ฉด ๋ ์ด์ ์ฐธ์กฐํ๋ ๊ณณ(์์์๋ p1)์ด ์์ผ๋ฉด postRepository๊ฐ ๋น์ด์๊ฒ ๋๋ค. 'p1 = null;'์ด ์๋๋ผ๋ ์๋์ ํ๋ค. (๋ ์ด์ ์ฐธ์กฐํ๋ ๊ณณ์ด ์๊ธฐ ๋๋ฌธ)
์ด์ ๋ฅผ ์ค๋ช ํ์๋ฉด getPostById๋ผ๋ ๋ฉ์๋ ์์์ CacheKey๊ฐ ์์ฑ๋๊ณ , ํด๋น ๋ฉ์๋๊ฐ ๋๋๋ฉด CacheKey๊ฐ ๋ ์ด์ ์ ํจํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค. ์ด ์๋ฆฌ๋ฅผ ์ด์ฉํ๋ฉด ์๋์ ๊ฐ์ด ํํํ ์๋ ์๋ค.
2. ๋ฐฉ๋ฒ 2
public class PostRepository{
private Map<CacheKey, Post> cache;
public PostRepository(){
this.cache = new WeakHashMap<>();
}
public Post getPostById(CacheKey key){
if(cache.containKey(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();
CacheKey key1 = new CacheKey(1);
postRepository.getPostById(key1);
assertFalse(postRepository.getCache().isEmpty());
key1 = null;
// TODO run gc
System.out.println("run gc");
System.gc(); // ํญ์ ์ด ์์ ์ gc๊ฐ ์คํ๋๋ค๋ ๋ณด์ฅ์ ์์ง๋ง, ์คํ์ด ๋๊ธฐ๋ ํ๋ค.
System.out.println("wait");
Thread.sleep(3000L);
assertTrue(postRepository.getCache().isEmpty());
}
}
key1์ ํด๋น ๋ฉ์๋๊ฐ ๋๋๊ธฐ ์ ๊น์ง ์ ํจํ๋ฐ 'key1 = null;'์ ํด์ฃผ์๊ธฐ ๋๋ฌธ์ ํ ์คํธ ์ฝ๋๊ฐ ์ ์ ์๋ํ๋ ๊ฒ์ ์ ์ ์๋ค. ๋ฐฉ๋ฒ 1๊ณผ ๋ค๋ฅด๊ฒ ์ด ๊ฒฝ์ฐ์ 'key1 = null;'์ด ์๋ค๋ฉด ์ ์ ์๋ํ์ง ์๋๋ค.
3. ๋ฐฉ๋ฒ 3
์ธ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ๊ฐ์ฒด๋ฅผ ๋ฃ๊ฑฐ๋ ๋นผ๊ฑฐ๋ ํ ๋ ์ง์ ๊ด๋ฆฌํ๋ ๊ฒ์ด๋ค. ์ ์ผ ์ค๋๋ ๊ฒ์ ์ฐพ์์ ์ญ์ ํ๊ฑฐ๋ LRU ์บ์ ๋ฑ์ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
4. ๋ฐฉ๋ฒ 4
๋ง์ง๋ง ๋ฐฉ๋ฒ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค.
public class PostRepository{
private Map<CacheKey, Post> cache;
public PostRepository(){
this.cache = new HashMap<>();
}
public Post getPostById(CacheKey key){
if(cache.containKey(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;
}
}
@Test
void backgroundThread() throws InterruptedException {
ScheduledExecutorService executor = Executors.newSchedultedThreadPool(1);
CacheKey1 = new CacheKey(1);
postRepostiory.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); // ์ฒ์ 1์ด ํ์, ๋งค 3์ด ๋ง๋ค ์คํํ๋๋ก ํ์๋ค.
Thread.sleep(20000L);
executor.shutdown();
}
20์ด ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์คํ๋๊ณ ์๋ ๋์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ 3์ด๋ง๋ค ๊ณ์ํด์ ์ ์ผ ๋ค์ ์๋ ๊ฐ์ฒด๋ฅผ ํ๋์ฉ ์ญ์ ํ๋ค.
3. ๋ฆฌ์ค๋(listener) ํน์ ์ฝ๋ฐฑ(callback)์์์ ๋์ & ํด๊ฒฐ์ฑ
public class ChatRoon {
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;
}
}
@Test
void chatRoom() 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);
}
ํด๋น ํ ์คํธ ์ฝ๋๋ฅผ ๊ทธ๋ฅ ์คํ์ํค๋ฉด ํ๋ฆฌ๊ฒ ๋๋ค. WeakReference๋ฅผ ์ญ์ ํด์ฃผ๋ ๊ธฐ๋ฅ์ WeakHashMap์ ๋ค์ด์๋ ๊ธฐ๋ฅ์ด๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋์ ๋ง์ฝ ์์ ํ ์คํธ ์ฝ๋๊ฐ ์ฑ๊ณตํ๊ธฐ ์ํด์๋ ์ปค์คํ ํ List๋ฅผ ๋ง๋ค ํ์๊ฐ ์๋ค.
List์์ WeakReference๊ฐ ์๋ ๊ฒฝ์ฐ๋ฅผ ์ค๋ช ํ๊ธฐ ์ํ ๊ฒ์ด์ง, ๋ฆฌ์ค๋ ํน์ ์ฝ๋ฐฑ์ ์ํฉ์์๋ weak reference๋ก ์ ์ฅํ๋ฉด ๊ฐ๋น์ง ์ปฌ๋ ํฐ๊ฐ ์ฆ์ ์๊ฑฐํด ๊ฐ๋ค.
ํด๋น ๊ธ์ ๋ฐฑ๊ธฐ์ ๋์ '์ดํํฐ๋ธ ์๋ฐ ์๋ฒฝ ๊ณต๋ต'์ ์๊ฐํ๊ณ ์์ฑํ ๊ฒ์ ๋๋ค.