Synchronized로 검색한 결과 :: 시소커뮤니티[SSISO Community]
 
SSISO 카페 SSISO Source SSISO 구직 SSISO 쇼핑몰 SSISO 맛집
추천검색어 : JUnit   Log4j   ajax   spring   struts   struts-config.xml   Synchronized   책정보   Ajax 마스터하기   우측부분

회원가입 I 비밀번호 찾기


SSISO Community검색
SSISO Community메뉴
[카페목록보기]
[블로그등록하기]  
[블로그리스트]  
SSISO Community카페
블로그 카테고리
정치 경제
문화 칼럼
비디오게임 스포츠
핫이슈 TV
포토 온라인게임
PC게임 에뮬게임
라이프 사람들
유머 만화애니
방송 1
1 1
1 1
1 1
1 1
1

Synchronized로 검색한 결과
등록일:2008-04-15 11:12:05
작성자:
제목:Java theory and practice: 유연한 참조로 메모리 누수 막기


캐싱에 제공되는 유연한 참조

developerWorks
문서 옵션

JavaScript가 필요한 문서 옵션은 디스플레이되지 않습니다.

수평출력으로 설정

이 페이지 출력

이 페이지를 이메일로 보내기

이 페이지를 이메일로 보내기

토론


제안 및 의견
피드백

난이도 : 초급

Brian Goetz, Principal Consultant, Quiotix

2006 년 3 월 14 일

지난 달에는 약한 참조(weak references)에 대해 설명했다. 이번 달에는 또 다른 형식의 참조 객체(Reference)인 유연한 참조(soft references)를 설명한다. 이것 역시 메모리 사용을 관리하고 잠재적인 메모리 누수를 없애는 보조 가비지 컬렉터이다.

가비지 컬렉션으로 자바 프로그램은 메모리 누수에 대해 면역성을 갖는다. 적어도 좁은 의미의 "메모리 누수 "의 경우가 그렇다는 것이다. 하지만 자바 프로그램의 객체 수명주기의 문제를 완전히 무시할 수 있다는 의미는 아니다. 자바 프로그램에서의 메모리 누수는 객체의 수명주기에 대한 주의가 부족할 때나 객체 수명 주기를 관리하는 표준 방식들을 파괴했을 때 발생한다. 예를 들어, 지난 시간에 는 우리는 객체의 수명주기를 분명히 정하지 못하면, 메타데이터와 일시적인 객체를 제휴하려고 할 때 의도하지 않은 객체 보유를 야기하게 된다는 것을 배웠다. 객체 수명주기 관리를 무시하거나 메모리 누수로 진행될 수 있는 다른 이디엄들도 있다.

객체 로이터링(Object loitering)

메모리 누수의 한 형태인 객체 로이터링은 Listing 1의 LeakyChecksum 클래스가 설명하고 있다. 이것은 getFileChecksum() 메소드를 제공하여 파일 컨텐트의 체크섬을 계산한다. getFileChecksum() 메소드는 파일 내용들을 버퍼로 읽어 들여 체크섬을 계산한다. 보다 단순한 구현은 버퍼를 getFileChecksum() 내의 로컬 변수로 할당하는 것이다. 인스턴스 필드에 버퍼를 캐싱하여 메모리 혼합을 줄인다. 이러한 "최적화 " 로는 부족하다. 객체 할당이 더 낫다. (또한 버퍼를 로컬 변수에서 인스턴스 변수로 승격시키면 추가적인 동기화 없이 더 이상의 쓰레드 보안 없는 클래스로 만든다. 단순한 구현은 getFileChecksum()Synchronized로 선언될 필요가 없고, 동시에 호출될 때 더 나은 확장성을 제공한다.)


Listing 1. "object loitering" 클래스
                
// BAD CODE - DO NOT EMULATE
public class LeakyChecksum {
private byte[] byteArray;

public Synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
if (byteArray == null || byteArray.length < len)
byteArray = new byte[len];
readFileContents(fileName, byteArray);
// calculate checksum and return it
}
}

이 클래스는 많은 문제들을 갖고 있지만 메모리 누수에 집중해 보자. 버퍼를 캐싱하려는 결정은 이것이 프로그램 내에서 여러 번 호출될 수 있고 재할당 보다는 버퍼를 재사용하는 것이 더 효율적이라는 가정에서 나온 것이다. 하지만 결과적으로 버퍼는 결코 릴리스 되지 않는다. (LeakyChecksum 객체가 가비지 컬렉션이 되지 않는 한) 프로그램을 통해 언제나 접근할 수 있기 때문이다. 더욱 나쁜 것은 이것은 줄어들 수 없기 때문에 LeakyChecksum은 영구적으로 버퍼를 보유하게 된다. 이는 가비지 컬렉터에 많은 부담을 주고 더 많은 컬렉션을 요구한다. 앞으로의 체크섬을 계산할 목적으로 큰 버퍼를 사용한다면 가장 비효율적인 일이 아닐 수 없다.

LeakyChecksum에서 문제의 원인은 버퍼가 논리적으로 getFileChecksum() 연산에만 국한되기 때문이다. 하지만 수명 주기는 인공적으로 늘어나서 인스턴스 필드까지 진입한다. 결과적으로 JVM 대신에 이 클래스가 버퍼의 수명 주기를 관리해야 한다.




위로


유연한 참조(soft reference)

이전 글에서 객체가 프로그램에 의해 사용되는 동안 객체에 접근하는 대안 방식을 약한 참조(weak references)가 제공하는 방법을 배웠다. 하지만 수명은 연장하지 않았다. Reference의 또 다른 하위 클래스인 유연한 참조(soft reference)는 다르면서도 관련된 목표를 수행한다. 약한 참조의 경우 애플리케이션이 가비지 컬렉션을 방해하지 않는 참조를 만들도록 한 반면 유연한 참조에서는 애플리케이션이 몇몇 객체를 "소모 가능한 것 "으로 위임하여 보조 가비지 컬렉터를 모으도록 하고 있다. 애플리케이션이 메모리를 사용하는지의 여부를 파악하는데 가비지 컬렉터가 제 기능을 하기 때문에 가용 메모리의 적절한 사용법을 결정하는 것은 애플리케이션에 달려있다. 애플리케이션이 객체 보유에 대해 그릇된 결정을 내린다면 퍼포먼스가 나빠진다. 왜냐하면 가비지 컬렉터는 애플리케이션이 메모리를 초과하여 실행되는 것을 막아야 하기 때문이다.

캐싱은 일반적인 퍼포먼스 최적화 방법이다. 애플리케이션이 이전 계산의 결과를 재사용 하는 것이 가능하다. 다시 계산을 할 필요가 없다. 캐싱은 CPU 활용과 메모리 사용은 서로 상쇄된다. 이상적인 균형은 얼마나 많은 메모리를 사용할 수 있는가에 달려있다. 너무 적은 캐싱으로는 원하는 퍼포먼스를 얻을 수 없다. 또한, 캐싱이 너무 많으면 퍼포먼스가 타격을 받는다. 너무 많은 메모리가 캐싱에 소비되기 때문에 다른 것에 쓰일 수 있는 것이 충분치 않기 때문이다. 메모리 수요를 결정할 때에는 애플리케이션 보다 가비지 컬렉터가 더 나은 위치에 있기 때문에 이러한 결정을 내릴 때에는 가비지 컬렉터의 도움을 받는 것이 합리적이다. 바로 이것이 유연한 참조가 하는 일이다.

객체에 유일하게 남아있는 참조가 약한 참조이거나 유연한 참조라면 그 객체는 유연하게 접근할 수 있다. 가비지 컬렉터는, 약하게 접근 할 수 있는 객체에 수행하는 것 처럼 이러한 객체들을 공격적으로 모으지 않는다. 대신 메모리가 정말 필요할 경우에만 유연하게 접근할 수 있는 객체를 모은다. 유연한 참조는 가비지 컬렉터에게 "메모리가 그렇게 적지 않다면 이 객체를 보유하고 싶다. 하지만 메모리가 부족하면 계속 진행하여 메모리를 모으면 처리하겠다. "라는 명령하는 방식이라고 할 수 있다. 가비지 컬렉터는 OutOfMemoryError를 던지기 전에 모든 유연한 참조를 제거해야 한다.

캐싱된 버퍼를 관리하는 유연한 참조를 사용하여 LeakyChecksum의 문제를 픽스할 수 있다.(Listing 2). 이제 메모리가 요구되지 않는 한 버퍼가 보유 되지만, 가비지 컬렉터에 의해 사용될 수 있다.


Listing 2. 유연한 참조로 LeakyChecksum 해결하기
                
public class CachingChecksum {
private SoftReference<byte[]> bufferRef;

public Synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
byte[] byteArray = bufferRef.get();
if (byteArray == null || byteArray.length < len) {
byteArray = new byte[len];
bufferRef.set(byteArray);
}
readFileContents(fileName, byteArray);
// calculate checksum and return it
}
}

캐시

CachingChecksum은 유연한 참조를 사용하여 한 개의 객체를 캐싱하고 JVM이 상세를 핸들하도록 하였다. 이와 비슷하게, GUI 애플리케이션에서 비트맵 그래픽을 캐싱하는데 유연한 참조가 사용된다. 유연한 참조가 사용될 수 있는지의 열쇠는 애플리케이션이 캐싱되는 데이터의 손실에서 회복될 수 있는지에 달려있다.

한 개 이상의 객체를 캐싱해야 한다면 Map을 사용하면 된다. 하지만 유연한 참조를 적용하는 방식에는 선택권이 있다. 캐싱을 Map<K, SoftReference<V>> 또는 SoftReference<Map<K,V>>로 서 관리할 수 있다. 대게는 후자 옵션이 사용된다. 컬렉터 작업이 수월하고 메모리 수요가 높을 때 전체 캐시를 수월하게 사용할 수 있기 때문이다. 가끔 캐시를 구현할 때 유연한 참조 대신 약한 참조가 사용되지만 이렇게 되면 캐싱 퍼포먼스는 나빠진다. 실제로, 약한 참조는 객체가 약하게 접근 가능한 것으로 된 후에 빠르게 제거될 것이다. 소소한 가비지 컬렉션들은 자주 실행되기 때문이다.

퍼포먼스를 위해 캐싱에 많이 의존하는 애플리케이션의 경우 유연한 참조는 너무 무디다. 유연한 종료, 복사, 트랜잭션 캐싱을 제공하는 보다 고급의 캐싱 프레임웍으로 대체해야 한다는 의미는 아니다. "값싸고 지저분한 " 캐싱 메커니즘으로서는 매력적인 장치이다.

약한 참조와 마찬가지로, 유연한 참조는 제휴 참조 큐를 사용하여 만들어지고, 가비지 컬렉터에 의해 생성될 때 이 참조가 인큐(enqueue)된다. 참조 큐는 약한 참조 만큼 유연한 참조에서는 유용하지 않다. 하지만 적은 메모리에서 애플리케이션이 시작할 때 관리 경고를 만드는데 사용될 수 있다.




위로


가비지 컬렉터가 Reference를 핸들하는 방법

약한 참조와 유연한 참조 모두 (팬텀 참조(phantom references)처럼) 추상 Reference 클래스를 확장한다. 참조 객체들은 가비지 컬렉터에 의해 특별하게 취급된다. 가비지 컬렉터가 힙을 추적하는 과정에서 Reference를 만나면 대상 객체를 마킹 또는 추적하지 않고 대신 알려진 활성 Reference 객체 큐에 그 Reference를 둔다. 트레이싱 후에 컬렉터는 유연하게 참조할 수 있는 객체들을 규명한다. 이러한 객체에는 강한 참조가 존재하지 않지만 유연한 참조는 존재한다. 가비지 컬렉터는 현재 컬렉션에 의해 사용된 메모리 양과 다른 정책 사항에 기반하여 유연한 참조가 이 시점에서 제거되었는지의 여부를 판단한다. 상응하는 참조 큐가 있다면 정리될 유연한 참조가 인큐된다. 남아있는 객체들은 루트 세트로서 취급되고 힙 트레이스는 이러한 루트들을 사용하여 지속되어 유연한 참조를 통해 접근 가능한 객체들이 마킹될 수 있도록 한다.

유연한 참조를 처리한 후에 약하게 접근 할 수 있는 객체 세트가 정의된다. 이 객체에는 강한 참조나 유연한 참조가 존재하지 않는다. 이들은 정리 및 인큐된다. 모든 Reference 유형들은 인큐 되기 전에 정리되어서 사후 클린업을 핸들하는 쓰레드는 대상 객체에 액세스 할 수 없다. 이 같은 이유로 Reference가 참조 큐와 연합하여 사용될 때, WeakHashMapMap.EntryWeakReference를 확장하는 것 처럼) 적절한 참조 유형을 분류하고 디자인에서 직접 사용하거나 클린업을 필요로 하는 엔터티에 대한 참조를 저장한다.

참조 프로세싱의 퍼포먼스 비용

참조 객체들은 가비지 컬렉션 프로세스에 추가 비용을 부과한다. 각 가비지 컬렉션에서 활성 Reference 객체들의 리스트가 만들어져야 하고 각 참조는 적절하게 처리되어 Reference 당 오버헤드를 각 컬렉션에 추가한다. 이 때 해당 객체가 컬렉팅 되는지의 여부는 상관이 없다. Reference 객체들은 가비지 컬렉션에 속해있고 해당 객체 전에 컬렉팅 될 수 있다. 이 같은 경우는 인큐가 되지 않는 경우이다.




위로


어레이 기반 컬렉션

스택이나 순환식 버퍼 같은 데이터 구조를 구현할 때 어레이가 사용될 때 또 다른 형태의 객체 로이터링이 발생한다. Listing 3의 LeakyStack 클래스는 어레이의 지원을 받는 스택의 구현 모습이다. pop() 메소드에서 상위 포인터가 감소된 후에도 elements는 스택에서 없어진 객체에 대한 참조를 여전히 유지하고 있다. 이 객체에 대한 참조는 프로그램에 의해서도 접근할 수 있다. 그 프로그램이 실제로 그 참조를 다시 사용하지 않더라도 말이다. 이 위치가 push()에 의해 재사용 될 때까지 객체의 가비지 컬렉션을 방지한다.


Listing 3. 어레이 기반 컬렉션에서의 객체 로이터링
                
public class LeakyStack {
private Object[] elements = new Object[MAX_ELEMENTS];
private int size = 0;

public void push(Object o) { elements[size++] = o; }

public Object pop() {
if (size == 0)
throw new EmptyStackException();
else {
Object result = elements[--size];
// elements[size+1] = null;
return result;
}
}
}

이 경우 객체 로이터링에 대한 해결책은 스택에서 제거된 후에 참조를 무효로 하는 것이다. (Listing 3). 하지만 클래스가 자신의 메모리를 관리하는 경우는 매우 드문 상황이다. 더 이상 필요하지 않는 객체를 무효로 하는 것이 좋다. 대부분, 공격적으로 참조를 무효화 하는 것에는 퍼포먼스나 메모리 사용에 있어서 이득이 없다. 형편 없는 퍼포먼스나 NullPointerException을 발생시킬 뿐이다. 이러한 알고리즘의 연결된 구현은 문제가 없다. 연결된 구현에서 링크 노드의 수명(그리고 저장될 객체에 대한 참조)은 객체가 컬렉션에 저장되는 동안 자동으로 묶인다. 약한 참조는 이 문제를 해결하는데 사용된다. 강한 참조 대신에 약한 참조 어레이를 관리한다. 하지만 실제로 LeakyStack는 자신의 메모리를 관리하고 더 이상 필요 없는 객체에 대한 참조가 제거되도록 한다. 어레이를 사용하여 스택이나 버퍼를 구현하는 것은 할당을 줄이는 최적화이지만 구현자에게는 큰 짐이다. 이 어레이에 저장된 참조들의 수명을 관리해야 하기 때문이다.




위로


요약

약한 참조와 마찬가지로 유연한 참조는 애플리케이션에서 객체 로이터링을 방지한다. 애플리케이션이 유연하게 참조된 객체의 손실을 감당할 수 있다면 유연한 참조가 알맞다.

기사의 원문보기



참고자료

교육

제품 및 기술 얻기

토론


필자소개


Brian Goetz는 18년 경력의 전문 소프트웨어 개발자이다. 캘리포니아 Los Altos에 위치한 소프트웨어 개발 및 컨설팅 회사인 Quiotix에서 Principal Consultant로 일하고 있으며, 여러 JCP 전문가 그룹에서도 활동하고 있다. 그의 저서 Java Concurrency In Practice 는 2006년 5월에 Addison-Wesley에서 출간되었다. Brian이 집필한 다양한 기술자료들도 참조하라.