Study/Effective-Java

[item#07] 다 쓴 객체 참조를 해제하라.

hongeeii 2023. 11. 27.
728x90
반응형

다 쓴 객체 참조를 해제하라.

어떤 객체에 대한 레퍼런스가 남아있다면 해당 객체는 가비지 컬렉션의 대상이 되지 않는다.

자기 메모리를 직접 관리하는 클래스면 메모리 누수에 주의! ex) 스택, 캐시, 리스너 또는 콜백

참조 객체를 null 처리하는 일은 예외적인 경우(하지만 생각보다 많다)이며 가장 좋은 방법은 유효 범위 밖으로 밀어내는 것이다.

  1. 적절한 자료구조써서 컨트롤

  2. 직접 null

  3. 직접 빼거나 넣거나

  4. 백그라운드 쓰레드써서 주기적으로 클린하는 작업을 실행

WeakHashMap

더이상 사용하지 않는 객체를 GC할 때 자동으로 삭제해주는 Map

  • Key가 더이상 강하게 레퍼런스되는 곳이 없다면 해당 엔트리를 제거한다.
  • 레퍼런스 종류
    • Strong, Soft, Weak, Phantom
  • 맵의 엔트리를 맵의 Value가 아니라 Key에 의존해야 하는 경우에 사용할 수 있다.
  • 캐시를 구현하는데 사용할 수 있지만, 캐시를 직접 구현하는 것은 권장하지 않는다.
// Strong
public List<WeakReference<User>> getUsers(){
  CharRoom localRoom = new ChatRoom(); // 처음엔 ChatRoom을 가르킴
  localRoom = null; // null이 되면서 Strong하게 Reference하는 애가 없어짐 -> 가비지 컬렉션 대상이 됨
  return users;
}

// Soft
public static void main(String[] args) throws InterruptedException {
    Object strong = new Object();
    SoftReference<Object> soft = new SoftReference<>(strong);
    strong = null;

    System.gc();
    // 메모리가 충분해서 없어지지 않는다.
}

// Weak
public static void main(String[] args) throws InterruptedException {
    Object strong = new Object();
    WeakReference<Object> weak = new WeakReference<>(strong);
    strong = null;

    System.gc();
    // 없어진다.
}

// Phantom
public static void main(String[] args) throws InterruptedException {
      BigObject strong = new BigObject();
      ReferenceQueue<BigObject> rq = new ReferenceQueue<>();

      // BigObjectReference<BigObject> phantom = new BigObjectReference<>(strong, rq);
      PhantomReference<BigObject> phantom = new PhantomReference<>(strong, rq);
      strong = null;

      System.gc();
      Thread.sleep(3000L);

      System.out.println(phantom.isEnqueued());

      Reference<? extends BigObject> reference = rq.poll();
      BigObjectReference bigObjectCleaner = (BigObjectReference) reference;
      bigObjectCleaner.cleanUp();
      reference.clear();
}

Weak, Soft Reference는 웬만하면 사용을 하지 않는다.(방법이 저것 밖에 없는 것 제외)

ScheduledThreadPoolExecutor

메소드 4종류

  • schedule(Runnable command, long delay, TimeUnit unit) : 작업을 일정 시간 뒤에 한번 실행합니다
  • schedule(Callable command, long delay, TimeUnit unit) : 작업을 일정 시간 뒤에 한번 실행하고, 그 결과를 리턴합니다.
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) : 작업을 일정 시간 간격으로 반복적으로 실행시킵니다.
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit) : 작업이 완료되면 일정 시간 뒤에 다시 실행시킵니다. scheduleAtFixedRate()와 다른 점은 작업 종료시점이 기준이라는 것입니다.

쓰레드 풀의 종류

  • newFixedThreadPool : 주어진 스레드 개수만큼 생성하고 그 수를 유지. 생성된 스레드 중 일부가 종료되었으면 스레드를 다시 생성.
  • newCachedThreadPool : 처리할 스레드가 많아지면 그만큼 스레드를 증가(최대 스레드 개수 : Integer.MAX_VALUE)
  • newSingleThreadExecutor : 스레드를 하나만 생성(1개로 계속 유지)
  • newScheduledThreadPool : 특정 시간 이후, 또는 주기적 작업 스레드 사용시 활용
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {
            service.submit(new Task2());
        }

        System.out.println(Thread.currentThread() + " hello");

        service.shutdown();
    }

    static class Task2 implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread() + " world");
        }
    }
스크린샷 2023-02-14 오후 9 45 05

Java Reference와 GC

https://d2.naver.com/helloworld/329631
Java의 GC는 공통적으로 크게 2가지 작업을 수행한다고 볼 수 있다.

  1. Heap내의 객체 중에서 가비지를 찾아낸다.
  2. 찾아낸 가비지를 처리해서 Heap의 메모리를 회수한다.

최초의 Java에서는 GC작업에 애플리케이션의 사용자 코드가 관여하지 않도록 구현되어 있었다.

그러나 위 2가지 작업에서 좀 더 다양한 방법으로 객체를 처리하려는 요구가 있었고, JDK1.2부터는 java.lang.ref 패키지를 추가해 제한적이나마 사용자 코드와 GC가 상호작용할 수 있게 하고 있다.

java.lang.ref 패키지는 전형적인 객체 참조인 strong reference 외에도 soft, weak, phantom 3가지의 새로운 참조 방식을 각각의 Reference 클래스로 제공한다.

이 3가지 Reference 클래스를 애플리케이션에 사용하면 앞서 설명하였듯이 GC에 일정 부분 관여할 수 있고, LRU(Least Recently Used) 캐시 같이 특별한 작업을 하는 애플리케이션을 더 쉽게 작성할 수 있다.

GC와 Reachability

GC는 객체가 가비지인지 판별하기 위해 reachability라는 개념을 사용한다.

어떤 객체에 유효한 참조가 있으면 'reachable'로, 없으면 'unreachable'로 구별하고, unreachable 객체를 가비지로 간주해 GC를 수행한다.

한 객체는 여러 다른 객체를 참조하고, 참조된 다른 객체들도 마찬가지로 또 다른 객체들을 참조할 수 있으므로 객체들은 참조 사슬을 이룬다.

이런 상황에서 유효한 참조 여부를 파악하려면 항상 유효한 최초의 참조가 있어야 하는데 이를 객체 참조의 root set이라고 한다.

힙에 있는 객체들에 대한 참조는 4가지 종류 중 하나다.

  • 힙 내의 다른 객체에 의한 참조
  • Java 스택, 즉 Java 메서드 실행 시에 사용하는 지역 변수와 파라미터들에 의한 참조
  • 네이티브 스택, 즉 JNI(Java Native Interface)에 의해 생성된 객체에 대한 참조
  • 메서드 영역의 정적 변수에 의한 참조

이들 중 힙 내의 다른 객체의 의한 참조를 제외한 나머지 3개가 root set으로, reachability를 판가름하는 기준이 된다.

root set으로부터 시작한 참조 사슬에 속한 객체들은 reachable 객체이고, 이 참조 사슬과 무관한 객체들이 unreachable 객체로 GC 대상이다.

728x90
반응형

추천 글