문제

쿼리 캐시에 도움이 될 수 있도록 검색 핸들 개체를 열어 두도록 권장하는 검색 라이브러리를 사용하고 있습니다.시간이 지남에 따라 캐시가 부풀어 오르는 경향이 있고(몇백 메가 정도 증가하고 계속 증가함) OOM이 시작되는 것을 관찰했습니다.이 캐시에 제한을 적용하거나 사용할 수 있는 메모리 양을 계획할 방법이 없습니다.그래서 양을 늘렸어요 Xmx 하지만 이는 문제에 대한 일시적인 해결책일 뿐입니다.

결국 나는 이 물건을 지시대상 ~의 java.lang.ref.SoftReference.따라서 시스템의 사용 가능한 메모리가 부족하면 개체가 사라지고 요청 시 새 개체가 생성됩니다.이렇게 하면 새로 시작한 후 속도가 약간 감소하지만 OOM을 실행하는 것보다 훨씬 더 나은 대안입니다.

SoftReferences에 대해 내가 본 유일한 문제는 참조 대상을 마무리하는 깔끔한 방법이 없다는 것입니다.내 경우에는 검색 핸들을 삭제하기 전에 이를 닫아야 합니다. 그렇지 않으면 시스템에 파일 설명자가 부족해질 수 있습니다.분명히 이 핸들을 다른 객체로 래핑하고 그 위에 종료자를 작성한 다음(또는 ReferenceQueue/PhantomReference에 연결) 놓아둘 수 있습니다.하지만 지구상의 모든 기사에서는 종료자 사용에 대해 조언하고 있으며 특히 파일 핸들을 해제하기 위한 종료자(예: 효과적인 자바 에드.II, 27페이지).

그래서 나는 다소 의아해합니다.이 모든 조언을 조심스럽게 무시하고 계속해야 할까요?그렇지 않으면 다른 실행 가능한 대안이 있습니까?미리 감사드립니다.

편집 #1:아래 텍스트는 Tom Hawtin이 제안한 대로 일부 코드를 테스트한 후 추가되었습니다.나에게는 제안이 작동하지 않거나 뭔가 빠진 것 같습니다.코드는 다음과 같습니다.

class Bloat {  // just a heap filler really
   private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

   private final int ii;

   public Bloat(final int ii) {
      this.ii = ii;
   }
}

// as recommended by Tom Hawtin
class MyReference<T> extends SoftReference<T> {
   private final T hardRef;

   MyReference(T referent, ReferenceQueue<? super T> q) {
      super(referent, q);
      this.hardRef = referent;
   }
}

//...meanwhile, somewhere in the neighbouring galaxy...
{
   ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
   Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
   int i=0;

   while(i<50000) {
//      set.add(new MyReference<Bloat>(new Bloat(i), rq));
      set.add(new SoftReference<Bloat>(new Bloat(i), rq));

//      MyReference<Bloat> polled = (MyReference<Bloat>) rq.poll();
      SoftReference<Bloat> polled = (SoftReference<Bloat>) rq.poll();

      if (polled != null) {
         Bloat polledBloat = polled.get();
         if (polledBloat == null) {
           System.out.println("is null :(");
         } else {
           System.out.println("is not null!");
         }
      }
      i++;
   }
}

위의 코드 조각을 실행하면 -Xmx10m 및 SoftReferences(위 코드에서와 같이), 나는 수많은 정보를 얻고 있습니다. is null :( 인쇄되었습니다.하지만 코드를 다음으로 바꾸면 MyReference (MyReference로 두 줄의 주석 처리를 제거하고 SoftReference로 한 줄의 주석 처리) 항상 OOM이 발생합니다.

조언에서 알 수 있듯이 내부에서 열심히 참고했습니다. MyReference 물체에 부딪히는 것을 방지해서는 안 된다 ReferenceQueue, 오른쪽?

도움이 되었습니까?

해결책

Tom의 답변은 정확하지만 질문에 추가된 코드는 Tom이 제안한 코드와 동일하지 않습니다.Tom이 제안한 내용은 다음과 같습니다.

class Bloat {  // just a heap filler really
    public Reader res;
    private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

    private final int ii;

    public Bloat(final int ii, Reader res) {
       this.ii = ii;
       this.res = res;
    }
 }

 // as recommended by Tom Hawtin
 class MySoftBloatReference extends SoftReference<Bloat> {
    public final Reader hardRef;

    MySoftBloatReference(Bloat referent, ReferenceQueue<Bloat> q) {
       super(referent, q);
       this.hardRef = referent.res;
    }
 }

 //...meanwhile, somewhere in the neighbouring galaxy...
 {
    ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
    Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
    int i=0;

    while(i<50000) {
        set.add(new MySoftBloatReference(new Bloat(i, new StringReader("test")), rq));

        MySoftBloatReference polled = (MySoftBloatReference) rq.poll();

        if (polled != null) {
            // close the reference that we are holding on to
            try {
                polled.hardRef.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        i++;
    }
}

큰 차이점은 하드 참조가 닫혀야 하는 개체에 대한 것이라는 점에 유의하세요.주변 개체는 가비지 수집될 수 있고 그렇게 되므로 OOM에 도달하지 않지만 참조를 닫을 수 있는 기회는 여전히 제공됩니다.루프를 벗어나면 해당 루프도 가비지 수집됩니다.물론 현실 세계에서는 아마도 성공하지 못할 것입니다. res 공개 인스턴스 멤버.

즉, 열린 파일 참조를 보유하고 있는 경우 메모리가 부족해지기 전에 해당 참조가 부족해질 위험이 매우 높습니다.또한 LRU 캐시를 사용하여 다음 이상을 유지하지 않을 수도 있습니다. 공중에 손가락을 집어넣는다 500개의 열린 파일.필요한 경우 가비지 수집이 가능하도록 MyReference 유형일 수도 있습니다.

MySoftBloatReference가 작동하는 방식을 조금 더 명확하게 설명하기 위해 기본 클래스인 SoftReference는 여전히 모든 메모리를 차지하는 개체에 대한 참조를 보유하고 있습니다.OOM이 발생하지 않도록 해제해야 하는 개체입니다.그러나 객체가 해제된 경우에도 Bloat가 사용하고 있는 리소스를 해제해야 합니다. 즉, Bloat는 두 가지 유형의 리소스인 메모리와 파일 핸들을 사용하고 있으며 이 두 리소스를 모두 해제해야 합니다. 또는 다음을 실행합니다. 리소스 중 하나 또는 다른 것에서.SoftReference는 해당 개체를 해제하여 메모리 리소스에 대한 부담을 처리하지만 다른 리소스인 파일 핸들도 해제해야 합니다.Bloat는 이미 해제되었기 때문에 이를 사용하여 관련 리소스를 해제할 수 없으므로 MySoftBloatReference는 닫아야 하는 내부 리소스에 대한 하드 참조를 유지합니다.Bloat가 해제되었다는 알림을 받은 후, 즉참조가 ReferenceQueue에 나타나면 MySoftBloatReference는 하드 참조를 통해 관련 리소스를 닫을 수도 있습니다.

편집하다: 클래스에 던져지면 컴파일되도록 코드를 업데이트했습니다.StringReader를 사용하여 해제해야 하는 외부 리소스를 나타내는 데 사용되는 Reader를 닫는 방법에 대한 개념을 설명합니다.이 특별한 경우 해당 스트림을 닫는 것은 사실상 아무 작업도 하지 않으므로 필요하지 않지만 필요한 경우 이를 수행하는 방법을 보여줍니다.

다른 팁

한정된 수의 리소스의 경우:아강 SoftReference.소프트 참조는 둘러싸는 객체를 가리켜야 합니다.하위 클래스의 강력한 참조는 리소스를 참조해야 하므로 항상 강력한 연결이 가능합니다.를 통해 읽으면 ReferenceQueue poll 리소스를 닫고 캐시에서 제거할 수 있습니다.캐시를 올바르게 해제해야 합니다(해당되는 경우). SoftReference 자체적으로는 가비지 수집되므로 큐에 추가할 수 없습니다. ReferenceQueue).

캐시에 해제되지 않은 제한된 수의 리소스만 있다는 점에 주의하십시오. 이전 항목을 제거하십시오(실제로 상황에 맞는 경우 유한한 캐시인 경우 소프트 참조를 삭제할 수 있습니다).일반적으로 더 중요한 것은 비메모리 리소스인 경우이며, 이 경우 외부 참조 개체가 없는 LRU 제거 캐시로 충분합니다.

(내 답변은 #1000입니다.London DevDay에서 게시됨.)

아.
(내가 아는 한) 막대기를 양쪽 끝에서 잡을 수는 없습니다.당신은 당신의 정보를 붙잡고 있거나, 아니면 그냥 놔두거나 둘 중 하나입니다.
하지만...마무리하는 데 도움이 되는 몇 가지 주요 정보를 보유할 수 있습니다.물론, 핵심 정보는 "실제 정보"보다 훨씬 작아야 하며 도달 가능한 개체 그래프에 실제 정보가 없어야 합니다(약한 참조가 도움이 될 수 있음).
기존 예제를 기반으로 구축(주요 정보 필드에 주의):

public class Test1 {
    static class Bloat {  // just a heap filler really
        private double a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;

        private final int ii;

        public Bloat(final int ii) {
            this.ii = ii;
        }
    }

    // as recommended by Tom Hawtin
    static class MyReference<T, K> extends SoftReference<T> {
        private final K keyInformation;

        MyReference(T referent, K keyInformation, ReferenceQueue<? super T> q) {
            super(referent, q);
            this.keyInformation = keyInformation;
        }

        public K getKeyInformation() {
            return keyInformation;
        }
    }

    //...meanwhile, somewhere in the neighbouring galaxy...
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
        Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
        int i = 0;

        while (i < 50000) {
            set.add(new MyReference<Bloat, Integer>(new Bloat(i), i, rq));

            final Reference<? extends Bloat> polled = rq.poll();

            if (polled != null) {
                if (polled instanceof MyReference) {
                    final Object keyInfo = ((MyReference) polled).getKeyInformation();
                    System.out.println("not null, got key info: " + keyInfo + ", finalizing...");
                } else {
                    System.out.println("null, can't finalize.");
                }
                rq.remove();
                System.out.println("removed reference");
            }

편집하다:
나는 "귀하의 정보를 보관하거나 놔두거나"에 대해 자세히 설명하고 싶습니다.귀하가 귀하의 정보를 보유할 수 있는 방법이 있다고 가정합니다.그러면 GC가 데이터 표시를 취소하게 되어 두 번째 GC 주기에서 작업이 완료된 후에만 데이터가 실제로 정리됩니다.이것은 가능합니다. 그리고 이것이 바로 finalize()의 목적입니다.두 번째 주기가 발생하는 것을 원하지 않는다고 명시했으므로 정보를 보유할 수 없습니다(if a-->b이면 !b-->!a).즉, 놓아주어야 한다는 뜻이다.

편집2:
실제로 두 번째 주기가 발생합니다. 그러나 "주요 팽창 데이터"가 아닌 "핵심 데이터"에 대한 것입니다.실제 데이터는 첫 번째 주기에서 지워집니다.

편집3:
분명히 실제 솔루션은 참조 큐에서 제거하기 위해 별도의 스레드를 사용합니다(전용 스레드에서 차단하는 poll(), 제거(), 차단하지 않음).

@Paul - 답변과 설명에 감사드립니다.

@Ran - 현재 코드에서는 루프 끝에 i++가 누락된 것 같습니다.또한 rq.poll()이 이미 최상위 참조를 제거하므로 루프에서 rq.remove()를 수행할 필요가 없습니다. 그렇지 않습니까?

몇 가지 사항:

1) OOM을 방지하기 위해 루프(Paul과 Ran의 솔루션 모두에 대해)에서 i++ 뒤에 Thread.sleep(1) 문을 추가해야 했지만 이는 큰 그림과 관련이 없으며 플랫폼에 따라 다릅니다.내 컴퓨터에는 쿼드 코어 CPU가 있고 Sun Linux 1.6.0_16 JDK를 실행하고 있습니다.

2) 이러한 솔루션을 살펴본 후에는 종료자를 계속 사용할 것 같습니다.Bloch의 책은 다음과 같은 이유를 제시합니다.

  • 종료자가 즉시 실행된다는 보장은 없으므로 종료자에서 시간이 중요한 작업을 수행하지 마십시오. 또한 SoftReerences에 대한 보장도 없습니다!
  • 중요한 지속 상태를 업데이트하기 위해 종료자에 의존하지 마십시오.
  • 종료자를 사용하면 심각한 성능 저하가 있습니다. 최악의 경우에는 분당 하나의 개체 정도를 종료하게 됩니다.나는 그것으로 살 수 있다고 생각합니다.
  • try/finally를 사용하세요. 아, 네, 꼭 그럴 거예요!

단순해 보이는 작업을 위해 막대한 양의 비계를 만들어야 한다는 것은 제가 보기에는 합리적이지 않습니다.내 말은, 문자 그대로, 그러한 코드를 보는 다른 사람에게는 분당 WTF 속도가 상당히 높을 것이라는 의미입니다.

3) 슬프게도, 바울, 톰, 달리기 사이에 포인트를 나누는 방법은 없습니다. 정확합니다.나는 Paul의 답변이 더 높은 평가를 받았고 더 자세한 설명이 있기 때문에 수락 플래그를 설정하고 있지만 Ran의 솔루션은 전혀 나쁘지 않으며 SoftReferences를 사용하여 구현하기로 선택한 경우 아마도 내 선택이 될 것입니다.고마워요!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top