item7 - 다 쓴 객체 참조를 해제하라
객체의 참조를 해제한다는 의미는 사용이 끝난 객체에 null 을 할당해 준다는 의미이다 GC 는 객체를 수거할 때 Mark, Sweep, Compact 이 3가지를 진행한다고 했는데, 이 중에서 다 쓴 객체라고 판단하고 mark를 진행하는 객체가 바로 해당 객체에 대한 참조가 더 이상 존재하지 않을 때이다
그래서 책에서 들어주는 예시로는 우선 스택이 있다 스택이라는 자료구조를 보면 배열이 존재하고, 해당 배열에서 하나씩 pop 한다고 생각했을 때 pop 한다고 해서 배열 내에 pop 된 객체가 어딘가로 사라지는 것이 아니라 계속해서 남아 있게 된다 그래서 위와 같은 케이스에서 객체 참조를 제외하기 위해서 사용하는 방법 중 하나는 pop 된 객체의 위치에 null 을 집어 넣어주는 것이다
물론 실제 현재 stack이 이렇게 되어있다는 건 아닌데, 이렇게 굳이 메모리를 신경쓰지 않고 개발을 진행했더라도 객체에 대한 주의를 가지고 개발을 진행해야 한다는 것이 포인트이다
두 번째로의 예시는 캐시를 언급한다 캐시 같은 경우는 위에서 처럼 Null 을 넣어주는 것이 없다 그럼 위에 처럼 계속해서 메모리에 객체는 쌓여만 갈 것인데 이런 케이스에서는 어떻게 처리해야하나 -> 여기서 말해주는건 WeekHashMap 이다 WeekHashMap 은 WeekReference 를 키로 가지는 HashMap 을 의미하고 이후에 다시 볼 예정 이것의 특징은 키가 더 이상 참조가 되지 않으면 키와 같이 키에 대한 value 도 GC가 진행될 때 같이 날라가는 그러한 특징을 가지고 있다
세 번째로는 직접 객체의 참조를 넣어거나 참조를 해제하는 과정을 직접 관리하는 방법이다 그 방식으로는 LRU(Least Recently Used) Cache 방법이 있다 LRU 는 페이지를 교체하는 데 있어서 가장 오랫동안 사용되지 않은 것을 교체 대상으로 삼는 그러한 알고리즘이다 LRU Cache 는 캐시에 공간이 부족할 때 가장 오랫 동안 사용하지 않은 놈을 제거하고 새로운 놈을 넣어주는 그러한 방식이다 상식적으로 가장 오랫 동안 사용되지 않았다는 의미는 앞으로도 사용될 가능성이 가장 낮은 놈이다 라고 판단하기 때문이다 실제로 이러한 알고리즘이 성능적인 면에서 보증 받기도 했고, 캐시 히트율을 높게 유지할 수 있었다고 한다
그래서 물론 LRU 와 같은 알고리즘이 있으면 사용하면 되겠지만 객체를 관리하는데 있어서 필요하다면, 직접 LRU 를 구현해서 관리하는 그러한 방식을 의미하는 것이다
4 번째로는 백그라운드 스레드를 사용해서 주기적으로 cleanup 을 실행하는 것을 구현하는 것이다 scheduled thread pool executor 가 의미하는 것이다 개념 자체는 배치와 같다고 생각하면 된다 따로 콜백이나 메소드를 호출함으로써 동작되도록 하는 것이 아니라 ScheduledExcutorService.scheduleAtFixedRate() 을 통해서 원하는 시간 마다 백그라운들 스레드를 통해서 동작하게 해주는 것이다 이외에도 리스너를 통해서 구현하는 것도 가능하다 리스너는 어떠한 이벤트에 대해서 발생하는 리스너로 map 과 같은 곳에 담아두었다가 이벤트가 발생했을 때 동작하게 해주는데, 이 리스너에 대한 cleanup 을 고려하지 않는다면, 이벤트가 메소드 범위를 넘어서지 않고 호출이 종료된다면, 그래도 메모리에 남아있는 그러한 문제가 생긴다 그래서 이건 또 Week Reference 을 통하면 그나마 쉽게 처리하는 것이 가능하다
NullPointerException
NullPointException 이 발생하는 이유는 우선, 메소드에서 null 이 발생하기 때문이랑, null 체크를 하지 않았기에 발생한다 가장 많이 보이는 NullPointException 은 equals() 메소드를 활용할 때 발생하는 것 같다 -> 물론 많은 null point exception 을 본 것은 아니지만 적은 경험 내에서는 equals()에서 가장 많이 발생했던 것 같다 여기에서 좋은 점으로는 npe 이 발생했을 때, 어디에서 발생하는지 파악하는 것이 쉬워서 잡는 것도 편한 편이다 처리하는 방법도 그냥 단순하게 equals으로 비교하는 대상이 null 인지 검증하는 그러한 단순한 작업이다 이외에도 다른 방법으로는 Exception 을 던져주던가, null 을 리턴하도록 하던가, optional 을 리턴하는 방법이 있다 optional은 자바8부터 사용할 수 있으며, 요놈은 ifPresent()나 isEmpty() 와 같은 메소드를 통해서 원하는 작업등을 사용해서 진행하는 것이 가능하다 optional은 null safe 하게 만들어주는 좋은 타입이지만 어찌되었든 한 번 감싸준 객체이기 때문에 primitive 타입보다는 무겁다는 특징이 존재한다 또한 사용할 때 메소드의 리턴타입으로는 사용하기 좋지만 매개변수와 같은 곳에서 받아서 사용하기에는 뭔가 이상하기도 하면서, 파라미터 또한 검증해야하는 귀찮음이 생긴다는 점도 기억하자 그것 이외에도 optional 로 list나 set와 같은 collection은 감쌀 필요가 없다 -> 이것은 list나 set에는 비어있는지를 검증하는 메소드가 이미 존재하고 있기 때문에 굳이 필요하지 않다는 점이다 그리고 primitive 타입을 optional로 감싸고 싶다면 OptionalInt, OptionalLong 과 같이 이미 제공되는 것들이 있기 때문에 참고
WeekHashMap -> soft reference, strong reference, week reference, phantom reference 더 이상 사용하지 않는 객체를 수거해가는 과정에서 위에서 이야기 한 것 처럼, 해당 키가 참조하는 value가 더 이상 존재하지 않는 것으로 확인되면 자동으로 수거해주는 그런 타입이다
ScheduledThreadPoolExecutor
Thread 는 Runnable 을 시작으로 Executor, ExecutorService 이렇게 구성이 되어있다 그렇기 때문에 우리는 ExecutorService 사용해서 Runnable, Callable 을 구현해서 스레드를 사용하곤 한다
기본적으로 new Thread(new Task()) 이렇게 선언해서 메인 스레드 이외의 스레드가 만들어진다. 그리고 해당 내부에 어떠한 작업은 Runnable 을 implement 하는 Task 클래스를 새로 만들고, Run 함수를 오버라이딩을 통해서 새로운 스레드에서 어떠한 작업을 할 것인지를 구현하고 thread.start() 을 통해서 스레드를 시작한다고 볼 수 있다
근데 스레드를 생성하는 것은 컴퓨터에 그만큼 부하를 준다는 점이다 -> 물론 필요가 있다면 사용하는 것이 맞지만 항상 성능면을 고려하면서 개발하는 것이 중요한 만큼 단순하게 스레드를 생성하는 것 이외에 스레드의 수를 조절할 수 있는 방법들이 있다 그래서 스레드를 100개 만드는 것 보다는 수를 적게 사용하면서, 해당 작업들을 비동기적으로 수행할 수 있는 방법이 ThreadPool 이다 ThreadPool에도 여러가지 종류가 있다 첫 번째로는 ExecutorService service = Executors.newFixedThreadPool(숫자) 요놈이다 이름만 봐도 조금은 보이겠지만 Fixed 이다. 어떠한 작업을 하든 input 값으로 설정한 숫자 만큼만을 가지고 스레드 작업을 수행하겠다는 의미이다 즉, 위의 예시인 100개의 스레드를 만들어서 각각 작업하는 것이 아닌 오직 10개의 스레드로만 어떻게든 작업을 수행하는 것이다 물론 그렇게 진행하게 되면 조금의 딜레이는 감수해야 하지만 그만큼 적은 리소스를 사용하는 것이다 그리고 Thread 에서 하는 것 처럼 new Task() 으로 해서 사용하는 것이 아닌 submit() 을 통해서 사용한다 두 번째로는 ExecutorService service = Executors.newCachedThreadPool() 이 있다 이건 우선적으로 이미 생성되어있고 놀고 있는 스레드를 선택해서 사용하는 방식이다. 만약 스레드가 없다면 새롭게 스레드를 만들어서 사용하는 방식이다 이 방식은 단 하나의 큐를 가지고 처리하는 방식이기 때문에 무한정으로 스레드를 생성할 수도 있는 그러한 문제가 있을 수 있다. 사용하기 위해서는 어느정도 알고, 고민하고 사용하자 세 번째로는 ExecutorService service = Executors.newSingleThreadExecutor() 가 있다 요건 사용하게 되면 오직 단 하나만의 스레드를 가지고 작업을 수행하게 된다. 물론 이것도 필요에 의해서 사용하면 좋을 것 같다 마지막으로는 ExecutorService service = Executors.newScheduledThreadPool(숫자) 이 있다 이것도 위의 fixed 와 같이 원하는 갯수의 스레드 만큼 생성하는 것이 가능하다 특징으로는 원하는 시간이나 주기에 원하는 작업을 새로운 스레드에서 진행하는 것이 가능한? 그러한 스레드 풀로 보인다
종류는 이렇게 볼 수 있고, 스레드를 사용하는데 있어서 내부의 구현 함수에도 종류가 2가지 있다 처음 언급한 Runnable 은 단순하게 스레드가 수행만 을 진행하는 그러한 함수이다 그렇기 떄문에 만약 작업한 값을 리턴받고 싶다면 Runnable을 implement 받는 것이 아니라 Callable<리턴타입> 으로 내부에서의 작업을 리턴받는 것이 가능하다 submit 을 통해서 작업을 수행하고, .get()을 통해서 리턴한 값을 받아서 사용하는 것이 가능하다
그냥 이렇게 단순하게 사용하는 것이 가능하니까 필요하면 더 공부해서 사용해보자!!
Last updated
Was this helpful?