Skip to content

taeyoung week02

tae0y edited this page Oct 2, 2022 · 2 revisions

아이템 7 - 다 쓴 객체 참조를 해제하라

  • 객체 참조를 더 사용하지 않아도 될 때 null로 초기화해준다.
  • 더 좋은 방법은 객체 참조를 담은 변수를 유효범위 밖으로 밀어내는 것이다.

안티패턴 - 참조가 살아있어 객체 메모리 회수 불가

  • 아래 Stack 코드는 객체를 배열에 저장
  • pop 메서드는 배열의 인덱스와 stack size만 줄이고 객체는 삭제하지 않음
  • 이에 따라 pop된 객체의 메모리가 회수되지 않는 문제가 발생함
public class Stack {
	private static final int DEFAULT_INITAL_CAPACITY = 16;

	private Obejct[] elements;
	private int size = 0;
	
	public Stack() {
		elements = new Object[DEFAULT_INITAL_CAPACITY];
	}

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

	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);
		}
}

바른패턴 - 다 쓴 참조 null 초기화, 유효범위 밖으로 등

  • 위 스택 코드의 pop 메서드에서 배열원소를 null로 초기화해줬더라면, 메모리가 정상적으로 회수되었을 것
  • 캐시의 경우 엔트리 메모리 회수를 언제할까?
    • 엔트리를 언제 파기해야할지 판단하기 어려운데, 보통은 aging 기법을 사용한다
    • 구현은 scheduled threadPoolExecutor 같은 백그라운드 스레드를 사용하거나
    • LinkedHashMap의 removeEldestEntry 메서드처럼 새 엔트리를 넣을 때 파기하기도
    • 파기로직을 직접 구현하려면 java.lang.ref 패키지를 활용한다
    • 혹은 WeakHashMap을 사용해 외부에서 키를 참조하는 동안만 엔트리를 유지할 수도 있다
  • 리스너/콜백의 경우 언제 해지하고 메모리를 회수해갈까?
    • 명확히 해지하지 않는다면 콜백은 쌓여만 가므로, 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다. 이를테면 WeakHashMap에 키로 저장한다.

그리고 조금더 알아보기

  • java.lang.ref 아래 SoftReference, WeakReference 제네릭 클래스가 있다.
//강한참조
Integer origin = Integer.valueOf(1);
origin = null;

//약한참조
Integer origin = Integer.valueOf(1);
WeakReference<Integer> weak = new WeakReference(origin);
origin = null; //weak도 GC대상이 된다

//부드러운참조
Integer origin = Integer.valueOf(1);
SoftReference<Integer> soft = new SoftReference(origin);
origin = null; //soft도 GC대상이긴한데, 메모리 여유가있으면 유지될수도 있다
  • GC와 RC, GC는 순환참조를 해결할 수 있는가? iOS) 타 언어의 GC와 Swift ARC의 차이 – 유셩장 (sihyungyou.github.io)

    • GC는 런타임에 메모리를 동적으로 감시하다가 메모리를 회수한다(JAVA)
    • RC는 컴파일시 메모리 해제 시점을 확인하고 코드를 넣는다(Swift)
      • 레퍼런스 카운트를 계산하고, 0이 되는 시점에 메모리를 해제한다.
      • RC는 메모리해제 시점을 정확히 알 수 있으므로, 메모리해제시 수행할 동작을 지정해줄 수 있다. Swift는 deinit 필드에 메모리 해제시 동작을 정의하는데, 이를테면 문자열 객체 파기시 쓰레기값을 지우는 보안코드를 정의해둘 수 있다. [crackingMobile/doit! ios swift - 기본개념.md at main · tae0y/crackingMobile (github.com)](https://github.com/tae0y/crackingMobile/blob/main/ios/doit! ios swift - 기본개념.md#메모리에-남지-않는-문자열-만들기)
    • GC의 경우 항상 메모리를 감시해야 하므로 메모리/CPU 오버헤드가 발생한다. 따라서 메모리/CPU가 데스크탑에 비해 제한적인 모바일 기기에서는 RC가 성능상 이점이 있다.
    • GC는 순환참조가 발생하더라도 메모리를 회수할 수 있다.
      • 힙 외부에서 접근할 수 있는 변수나 오브젝트를 GC root 아래 트리 형태로 관리한다
      • root에서 도달하지 못하는 객체를 메모리에서 해제하므로, 순환참조가 발생해도 메모리를 회수할 수 있다. mark-and-sweep
  • GC와 관련지어 더 깊이있게 고민해보기 [book-effective-java/7_다 쓴 객체 참조를 해제하라_김석래.pdf at main · Meet-Coder-Study/book-effective-java (github.com)](https://github.com/Meet-Coder-Study/book-effective-java/blob/main/2장/7_다 쓴 객체 참조를 해제하라_김석래.pdf)

    • GC 세부적인 로직
      • mark/swipe
      • unreachable
      • young/old aging, max aging threashold
    • GC는 언제 호출되는가? 메모리가 부족할때, 또는 gc호출됐을때
    • GC는 언제 메모리를 회수하는가? 메서드/스택영역에서 호출하지 않는 경우
    • 메모리 모니터링 : jstat, visualVM, visualGC