为什么在使用最终确定性时收集了bytebuffer?

发布于 2025-01-28 09:24:18 字数 2606 浏览 2 评论 0原文

我有一个缓冲池实现,该实现基本上通过Allocate()/Release() api提供了预先分配的字节案对象。为了检测呼叫者忘记拨打释放并且ByteBuffer Ref泄漏的情况,我正在使用Guava的finalizableferencequeuefinalizablePhantomReference结合使用。 finalizeretrent()

此外,我需要选择性地破坏缓冲池,并用不同的配置替换较新的池。为此,我将以前的SampleBufferpool引用null,让垃圾收集器完成工作。但是,我注意到bytebuffer没有被收集/最终定义。 (我验证了完整的GC暂停未通过添加-XX:+printgcdetails -verbose收集任何内存:GC JVM标志)

package foo;

import com.google.common.base.FinalizablePhantomReference;
import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.collect.Sets;
import java.lang.ref.Reference;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.IntStream;


public class App {
  public static class SampleBufferPool {
    // Phantom reference queue for detecting memory leaks
    // See. https://guava.dev/releases/19.0/api/docs/com/google/common/base/FinalizableReferenceQueue.html
    private static final FinalizableReferenceQueue FRQ = new FinalizableReferenceQueue();
    // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
    public static final Set<Reference<?>> REFERENCES = Sets.newConcurrentHashSet();

    private final ConcurrentLinkedDeque<ByteBuffer> _bufferCache = new ConcurrentLinkedDeque<>();
    private final int _chunkSize;
    private final int _numChunks;

    public SampleBufferPool(int chunkSize, int numChunks) {
      _chunkSize = chunkSize;
      _numChunks = numChunks;
      IntStream.range(0, _numChunks).forEach(i -> populateSingleChunk());
    }

    public ByteBuffer allocate() {
      return _bufferCache.pollLast();
    }

    public void release(ByteBuffer chunk) {
      _bufferCache.offerLast(chunk);
    }

    private void populateSingleChunk() {
      ByteBuffer chunk = ByteBuffer.allocate(_chunkSize);

      _bufferCache.offerLast(chunk);

      Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
        @Override
        public void finalizeReferent() {
          REFERENCES.remove(this);
          System.out.println("LEAK DETECTED. ByteBuf[" + "] from RecyclingMemoryPool");
        }
      };
      REFERENCES.add(reference);
    }
  }

  public static void main(String[] args) {
    SampleBufferPool sampleBufferPool = new SampleBufferPool(20000000, 400);
    sampleBufferPool = null;

    for (int i = 0; i < 10; i++) {
      System.gc();
    }
  }
}

I have a buffer pool implementation which basically provides pre-allocated ByteBuffer objects via allocate()/release() API. In order to detect the cases when caller forgot to call release and the ByteBuffer ref is leaked, I am using Guava's FinalizableReferenceQueue in conjunction with FinalizablePhantomReference. finalizeReferent().

Additionally, I need to selectively destroy the buffer pool and replace it with a newer one with a different configuration. For that, I was setting previous SampleBufferPool reference to null and let garbage collector do its job. However, I noticed that the ByteBuffer were not getting collected/finalizeReferent is not being called. (I verified that the full GC pause are not collecting any memory via adding -XX:+PrintGCDetails -verbose:gc JVM flags)

package foo;

import com.google.common.base.FinalizablePhantomReference;
import com.google.common.base.FinalizableReferenceQueue;
import com.google.common.collect.Sets;
import java.lang.ref.Reference;
import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.IntStream;


public class App {
  public static class SampleBufferPool {
    // Phantom reference queue for detecting memory leaks
    // See. https://guava.dev/releases/19.0/api/docs/com/google/common/base/FinalizableReferenceQueue.html
    private static final FinalizableReferenceQueue FRQ = new FinalizableReferenceQueue();
    // This ensures that the FinalizablePhantomReference itself is not garbage-collected.
    public static final Set<Reference<?>> REFERENCES = Sets.newConcurrentHashSet();

    private final ConcurrentLinkedDeque<ByteBuffer> _bufferCache = new ConcurrentLinkedDeque<>();
    private final int _chunkSize;
    private final int _numChunks;

    public SampleBufferPool(int chunkSize, int numChunks) {
      _chunkSize = chunkSize;
      _numChunks = numChunks;
      IntStream.range(0, _numChunks).forEach(i -> populateSingleChunk());
    }

    public ByteBuffer allocate() {
      return _bufferCache.pollLast();
    }

    public void release(ByteBuffer chunk) {
      _bufferCache.offerLast(chunk);
    }

    private void populateSingleChunk() {
      ByteBuffer chunk = ByteBuffer.allocate(_chunkSize);

      _bufferCache.offerLast(chunk);

      Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
        @Override
        public void finalizeReferent() {
          REFERENCES.remove(this);
          System.out.println("LEAK DETECTED. ByteBuf[" + "] from RecyclingMemoryPool");
        }
      };
      REFERENCES.add(reference);
    }
  }

  public static void main(String[] args) {
    SampleBufferPool sampleBufferPool = new SampleBufferPool(20000000, 400);
    sampleBufferPool = null;

    for (int i = 0; i < 10; i++) {
      System.gc();
    }
  }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

酒与心事 2025-02-04 09:24:18

您正在创建一个finalizablePhantomReference的子类,作为非静态上下文中的匿名子类:

Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
  @Override
  public void finalizeReferent() {
    REFERENCES.remove(this);
    System.out.println("LEAK DETECTED. ByteBuf[" + "] from RecyclingMemoryPool");
  }
};

在JDK&nbsp; 18之前,匿名内部类始终保留对周围实例的引用,无论他们是否使用。如 bug报告用Javac编译源代码。由于这仍然是一项编译器的特定行为,而且更容易偶然使用周围类的成员,这将迫使对实例进行参考,因此您应该使用static上下文。例如

private void populateSingleChunk() {
  ByteBuffer chunk = ByteBuffer.allocate(_chunkSize);

  _bufferCache.offerLast(chunk);

  registerChunk(chunk);
}
private static void registerChunk(ByteBuffer chunk) {
  Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
    @Override
    public void finalizeReferent() {
      REFERENCES.remove(this);
      System.out.println("LEAK DETECTED. ByteBuf[" + "] from RecyclingMemoryPool");
    }
  };
  REFERENCES.add(reference);
}

,当然,幻影参考对象也不得对引用对象有强有力的参考。如果您需要参考物的属性,则必须事先提取它们,例如

private void populateSingleChunk() {
  ByteBuffer chunk = ByteBuffer.allocate(_chunkSize);

  _bufferCache.offerLast(chunk);

  registerChunk(chunk);
}
private static void registerChunk(ByteBuffer chunk) {
  int capacity = chunk.capacity();
  Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
    @Override
    public void finalizeReferent() {
      REFERENCES.remove(this);
      System.out.println("LEAK DETECTED. ByteBuf[capacity = "
                       + capacity + "] from RecyclingMemoryPool");
    }
  };
  REFERENCES.add(reference);
}

You are creating a subclass of FinalizablePhantomReference as anonymous subclass inside a non-static context:

Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
  @Override
  public void finalizeReferent() {
    REFERENCES.remove(this);
    System.out.println("LEAK DETECTED. ByteBuf[" + "] from RecyclingMemoryPool");
  }
};

Prior to JDK 18, anonymous inner classes always keep a reference to their surrounding instance, whether they are using it or not. As described by bug report JDK-8271717, this changes with JDK 18 when compiling the source code with javac. Since this still is a compiler specific behavior and further, it is too easy to use a member of the surrounding class by accident, which would force keeping a reference to the instance, you should use a static context. E.g.

private void populateSingleChunk() {
  ByteBuffer chunk = ByteBuffer.allocate(_chunkSize);

  _bufferCache.offerLast(chunk);

  registerChunk(chunk);
}
private static void registerChunk(ByteBuffer chunk) {
  Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
    @Override
    public void finalizeReferent() {
      REFERENCES.remove(this);
      System.out.println("LEAK DETECTED. ByteBuf[" + "] from RecyclingMemoryPool");
    }
  };
  REFERENCES.add(reference);
}

Of course, the phantom reference object must not have strong references to the referent as well. If you need properties of the referent, you must extract them beforehand, e.g.

private void populateSingleChunk() {
  ByteBuffer chunk = ByteBuffer.allocate(_chunkSize);

  _bufferCache.offerLast(chunk);

  registerChunk(chunk);
}
private static void registerChunk(ByteBuffer chunk) {
  int capacity = chunk.capacity();
  Reference<?> reference = new FinalizablePhantomReference<>(chunk, FRQ) {
    @Override
    public void finalizeReferent() {
      REFERENCES.remove(this);
      System.out.println("LEAK DETECTED. ByteBuf[capacity = "
                       + capacity + "] from RecyclingMemoryPool");
    }
  };
  REFERENCES.add(reference);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文