All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.io.ArrayRetainableByteBufferPool Maven / Gradle / Ivy

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.io;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntUnaryOperator;
import java.util.stream.Collectors;

import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Pool;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

A {@link RetainableByteBuffer} pool where RetainableByteBuffers are held in {@link Pool}s that are * held in array elements.

*

Given a capacity {@code factor} of 1024, the first array element holds a Pool of RetainableByteBuffers * each of capacity 1024, the second array element holds a Pool of RetainableByteBuffers each of capacity * 2048, and so on.

*

The {@code maxHeapMemory} and {@code maxDirectMemory} default heuristic is to use {@link Runtime#maxMemory()} * divided by 4.

*/ @SuppressWarnings("resource") @ManagedObject public class ArrayRetainableByteBufferPool implements RetainableByteBufferPool, Dumpable { private static final Logger LOG = LoggerFactory.getLogger(ArrayRetainableByteBufferPool.class); private final RetainedBucket[] _direct; private final RetainedBucket[] _indirect; private final int _minCapacity; private final int _maxCapacity; private final long _maxHeapMemory; private final long _maxDirectMemory; private final AtomicLong _currentHeapMemory = new AtomicLong(); private final AtomicLong _currentDirectMemory = new AtomicLong(); private final IntUnaryOperator _bucketIndexFor; /** * Creates a new ArrayRetainableByteBufferPool with a default configuration. * Both {@code maxHeapMemory} and {@code maxDirectMemory} default to 0 to use default heuristic. */ public ArrayRetainableByteBufferPool() { this(0, -1, -1, Integer.MAX_VALUE); } /** * Creates a new ArrayRetainableByteBufferPool with the given configuration. * Both {@code maxHeapMemory} and {@code maxDirectMemory} default to 0 to use default heuristic. * * @param minCapacity the minimum ByteBuffer capacity * @param factor the capacity factor * @param maxCapacity the maximum ByteBuffer capacity * @param maxBucketSize the maximum number of ByteBuffers for each bucket */ public ArrayRetainableByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxBucketSize) { this(minCapacity, factor, maxCapacity, maxBucketSize, 0L, 0L); } /** * Creates a new ArrayRetainableByteBufferPool with the given configuration. * * @param minCapacity the minimum ByteBuffer capacity * @param factor the capacity factor * @param maxCapacity the maximum ByteBuffer capacity * @param maxBucketSize the maximum number of ByteBuffers for each bucket * @param maxHeapMemory the max heap memory in bytes, -1 for unlimited memory or 0 to use default heuristic * @param maxDirectMemory the max direct memory in bytes, -1 for unlimited memory or 0 to use default heuristic */ public ArrayRetainableByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory) { this(minCapacity, factor, maxCapacity, maxBucketSize, null, null, maxHeapMemory, maxDirectMemory); } /** * Creates a new ArrayRetainableByteBufferPool with the given configuration. * * @param minCapacity the minimum ByteBuffer capacity * @param factor the capacity factor * @param maxCapacity the maximum ByteBuffer capacity * @param maxBucketSize the maximum number of ByteBuffers for each bucket * @param maxHeapMemory the max heap memory in bytes, -1 for unlimited memory or 0 to use default heuristic * @param maxDirectMemory the max direct memory in bytes, -1 for unlimited memory or 0 to use default heuristic * @param bucketIndexFor a {@link Function} that takes a capacity and returns a bucket index * @param bucketCapacity a {@link Function} that takes a bucket index and returns a capacity * @deprecated use {@link #ArrayRetainableByteBufferPool(int, int, int, int, IntUnaryOperator, IntUnaryOperator, long, long)} * instead */ @Deprecated protected ArrayRetainableByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory, Function bucketIndexFor, Function bucketCapacity) { this(minCapacity, factor, maxCapacity, maxBucketSize, bucketIndexFor::apply, bucketCapacity::apply, maxHeapMemory, maxDirectMemory); } /** * Creates a new ArrayRetainableByteBufferPool with the given configuration. * * @param minCapacity the minimum ByteBuffer capacity * @param factor the capacity factor * @param maxCapacity the maximum ByteBuffer capacity * @param maxBucketSize the maximum number of ByteBuffers for each bucket * @param bucketIndexFor a {@link IntUnaryOperator} that takes a capacity and returns a bucket index * @param bucketCapacity a {@link IntUnaryOperator} that takes a bucket index and returns a capacity * @param maxHeapMemory the max heap memory in bytes, -1 for unlimited memory or 0 to use default heuristic * @param maxDirectMemory the max direct memory in bytes, -1 for unlimited memory or 0 to use default heuristic */ protected ArrayRetainableByteBufferPool(int minCapacity, int factor, int maxCapacity, int maxBucketSize, IntUnaryOperator bucketIndexFor, IntUnaryOperator bucketCapacity, long maxHeapMemory, long maxDirectMemory) { if (minCapacity <= 0) minCapacity = 0; factor = factor <= 0 ? AbstractByteBufferPool.DEFAULT_FACTOR : factor; if (maxCapacity <= 0) maxCapacity = AbstractByteBufferPool.DEFAULT_MAX_CAPACITY_BY_FACTOR * factor; if ((maxCapacity % factor) != 0 || factor >= maxCapacity) throw new IllegalArgumentException(String.format("The capacity factor(%d) must be a divisor of maxCapacity(%d)", factor, maxCapacity)); int f = factor; if (bucketIndexFor == null) bucketIndexFor = c -> (c - 1) / f; if (bucketCapacity == null) bucketCapacity = i -> (i + 1) * f; int length = bucketIndexFor.applyAsInt(maxCapacity) + 1; RetainedBucket[] directArray = new RetainedBucket[length]; RetainedBucket[] indirectArray = new RetainedBucket[length]; for (int i = 0; i < directArray.length; i++) { int capacity = Math.min(bucketCapacity.applyAsInt(i), maxCapacity); directArray[i] = new RetainedBucket(capacity, maxBucketSize); indirectArray[i] = new RetainedBucket(capacity, maxBucketSize); } _minCapacity = minCapacity; _maxCapacity = maxCapacity; _direct = directArray; _indirect = indirectArray; _maxHeapMemory = AbstractByteBufferPool.retainedSize(maxHeapMemory); _maxDirectMemory = AbstractByteBufferPool.retainedSize(maxDirectMemory); _bucketIndexFor = bucketIndexFor; } @ManagedAttribute("The minimum pooled buffer capacity") public int getMinCapacity() { return _minCapacity; } @ManagedAttribute("The maximum pooled buffer capacity") public int getMaxCapacity() { return _maxCapacity; } @Override public RetainableByteBuffer acquire(int size, boolean direct) { RetainedBucket bucket = bucketFor(size, direct); if (bucket == null) return newRetainableByteBuffer(size, direct, this::removed); RetainedBucket.Entry entry = bucket.acquire(); RetainableByteBuffer buffer; if (entry == null) { RetainedBucket.Entry reservedEntry = bucket.reserve(); if (reservedEntry != null) { buffer = newRetainableByteBuffer(bucket._capacity, direct, retainedBuffer -> { BufferUtil.reset(retainedBuffer.getBuffer()); reservedEntry.release(); }); reservedEntry.enable(buffer, true); if (direct) _currentDirectMemory.addAndGet(buffer.capacity()); else _currentHeapMemory.addAndGet(buffer.capacity()); releaseExcessMemory(direct); } else { buffer = newRetainableByteBuffer(size, direct, this::removed); } } else { buffer = entry.getPooled(); buffer.acquire(); } return buffer; } protected ByteBuffer allocate(int capacity) { return ByteBuffer.allocate(capacity); } protected ByteBuffer allocateDirect(int capacity) { return ByteBuffer.allocateDirect(capacity); } protected void removed(RetainableByteBuffer retainedBuffer) { } private RetainableByteBuffer newRetainableByteBuffer(int capacity, boolean direct, Consumer releaser) { ByteBuffer buffer = direct ? allocateDirect(capacity) : allocate(capacity); BufferUtil.clear(buffer); RetainableByteBuffer retainableByteBuffer = new RetainableByteBuffer(buffer, releaser); retainableByteBuffer.acquire(); return retainableByteBuffer; } protected Pool poolFor(int capacity, boolean direct) { return bucketFor(capacity, direct); } private RetainedBucket bucketFor(int capacity, boolean direct) { if (capacity < _minCapacity) return null; int idx = _bucketIndexFor.applyAsInt(capacity); RetainedBucket[] buckets = direct ? _direct : _indirect; if (idx >= buckets.length) return null; return buckets[idx]; } @ManagedAttribute("The number of pooled direct ByteBuffers") public long getDirectByteBufferCount() { return getByteBufferCount(true); } @ManagedAttribute("The number of pooled heap ByteBuffers") public long getHeapByteBufferCount() { return getByteBufferCount(false); } private long getByteBufferCount(boolean direct) { RetainedBucket[] buckets = direct ? _direct : _indirect; return Arrays.stream(buckets).mapToLong(RetainedBucket::size).sum(); } @ManagedAttribute("The number of pooled direct ByteBuffers that are available") public long getAvailableDirectByteBufferCount() { return getAvailableByteBufferCount(true); } @ManagedAttribute("The number of pooled heap ByteBuffers that are available") public long getAvailableHeapByteBufferCount() { return getAvailableByteBufferCount(false); } private long getAvailableByteBufferCount(boolean direct) { RetainedBucket[] buckets = direct ? _direct : _indirect; return Arrays.stream(buckets).mapToLong(bucket -> bucket.values().stream().filter(Pool.Entry::isIdle).count()).sum(); } @ManagedAttribute("The bytes retained by direct ByteBuffers") public long getDirectMemory() { return getMemory(true); } @ManagedAttribute("The bytes retained by heap ByteBuffers") public long getHeapMemory() { return getMemory(false); } private long getMemory(boolean direct) { if (direct) return _currentDirectMemory.get(); else return _currentHeapMemory.get(); } @ManagedAttribute("The available bytes retained by direct ByteBuffers") public long getAvailableDirectMemory() { return getAvailableMemory(true); } @ManagedAttribute("The available bytes retained by heap ByteBuffers") public long getAvailableHeapMemory() { return getAvailableMemory(false); } private long getAvailableMemory(boolean direct) { RetainedBucket[] buckets = direct ? _direct : _indirect; long total = 0L; for (RetainedBucket bucket : buckets) { int capacity = bucket._capacity; total += bucket.values().stream().filter(Pool.Entry::isIdle).count() * capacity; } return total; } @ManagedOperation(value = "Clears this RetainableByteBufferPool", impact = "ACTION") public void clear() { clearArray(_direct, _currentDirectMemory); clearArray(_indirect, _currentHeapMemory); } private void clearArray(RetainedBucket[] poolArray, AtomicLong memoryCounter) { for (RetainedBucket pool : poolArray) { for (RetainedBucket.Entry entry : pool.values()) { if (entry.remove()) { memoryCounter.addAndGet(-entry.getPooled().capacity()); removed(entry.getPooled()); } } } } private void releaseExcessMemory(boolean direct) { long maxMemory = direct ? _maxDirectMemory : _maxHeapMemory; if (maxMemory > 0) { long excess = getMemory(direct) - maxMemory; if (excess > 0) evict(direct, excess); } } /** * This eviction mechanism searches for the RetainableByteBuffers that were released the longest time ago. * @param direct true to search in the direct buffers buckets, false to search in the heap buffers buckets. * @param excess the amount of bytes to evict. At least this much will be removed from the buckets. */ private void evict(boolean direct, long excess) { if (LOG.isDebugEnabled()) LOG.debug("evicting {} bytes from {} pools", excess, (direct ? "direct" : "heap")); long now = NanoTime.now(); long totalClearedCapacity = 0L; RetainedBucket[] buckets = direct ? _direct : _indirect; while (totalClearedCapacity < excess) { // Run through all the buckets to avoid removing // the buffers only from the first bucket(s). for (RetainedBucket bucket : buckets) { RetainedBucket.Entry oldestEntry = findOldestEntry(now, bucket); if (oldestEntry == null) continue; if (oldestEntry.remove()) { RetainableByteBuffer buffer = oldestEntry.getPooled(); int clearedCapacity = buffer.capacity(); if (direct) _currentDirectMemory.addAndGet(-clearedCapacity); else _currentHeapMemory.addAndGet(-clearedCapacity); totalClearedCapacity += clearedCapacity; removed(buffer); } // else a concurrent thread evicted the same entry -> do not account for its capacity. } } if (LOG.isDebugEnabled()) LOG.debug("eviction done, cleared {} bytes from {} pools", totalClearedCapacity, (direct ? "direct" : "heap")); } @Override public String toString() { return String.format("%s{min=%d,max=%d,buckets=%d,heap=%d/%d,direct=%d/%d}", super.toString(), _minCapacity, _maxCapacity, _direct.length, _currentHeapMemory.get(), _maxHeapMemory, _currentDirectMemory.get(), _maxDirectMemory); } @Override public void dump(Appendable out, String indent) throws IOException { Dumpable.dumpObjects( out, indent, this, DumpableCollection.fromArray("direct", _direct), DumpableCollection.fromArray("indirect", _indirect)); } private Pool.Entry findOldestEntry(long now, Pool bucket) { // This method may be in the hot path, do not use Java streams. RetainedBucket.Entry oldestEntry = null; RetainableByteBuffer oldestBuffer = null; long oldestAge = 0; for (RetainedBucket.Entry entry : bucket.values()) { RetainableByteBuffer buffer = entry.getPooled(); // A null buffer means the entry is reserved // but not acquired yet, try the next. if (buffer != null) { long age = NanoTime.elapsed(buffer.getLastUpdate(), now); if (oldestBuffer == null || age > oldestAge) { oldestEntry = entry; oldestBuffer = buffer; oldestAge = age; } } } return oldestEntry; } private static class RetainedBucket extends Pool { private final int _capacity; RetainedBucket(int capacity, int size) { super(Pool.StrategyType.THREAD_ID, size, true); _capacity = capacity; } @Override public String toString() { int entries = 0; int inUse = 0; for (Entry entry : values()) { entries++; if (entry.isInUse()) inUse++; } return String.format("%s{capacity=%d,inuse=%d(%d%%)}", super.toString(), _capacity, inUse, entries > 0 ? (inUse * 100) / entries : 0); } } /** *

A variant of {@link ArrayRetainableByteBufferPool} that tracks buffer * acquires/releases, useful to identify buffer leaks.

*

Use {@link #getLeaks()} when the system is idle to get * the {@link Buffer}s that have been leaked, which contain * the stack trace information of where the buffer was acquired.

*/ public static class Tracking extends ArrayRetainableByteBufferPool { private static final Logger LOG = LoggerFactory.getLogger(Tracking.class); private final Set buffers = ConcurrentHashMap.newKeySet(); public Tracking() { super(); } public Tracking(int minCapacity, int factor, int maxCapacity, int maxBucketSize) { super(minCapacity, factor, maxCapacity, maxBucketSize); } public Tracking(int minCapacity, int factor, int maxCapacity, int maxBucketSize, long maxHeapMemory, long maxDirectMemory) { super(minCapacity, factor, maxCapacity, maxBucketSize, maxHeapMemory, maxDirectMemory); } public Tracking(int minCapacity, int factor, int maxCapacity, int maxBucketSize, IntUnaryOperator bucketIndexFor, IntUnaryOperator bucketCapacity, long maxHeapMemory, long maxDirectMemory) { super(minCapacity, factor, maxCapacity, maxBucketSize, bucketIndexFor, bucketCapacity, maxHeapMemory, maxDirectMemory); } @Override public RetainableByteBuffer acquire(int size, boolean direct) { RetainableByteBuffer buffer = super.acquire(size, direct); Buffer wrapper = new Buffer(buffer, size); if (LOG.isDebugEnabled()) LOG.debug("acquired {}", wrapper); buffers.add(wrapper); return wrapper; } public Set getLeaks() { return buffers; } public String dumpLeaks() { return getLeaks().stream() .map(Buffer::dump) .collect(Collectors.joining(System.lineSeparator())); } public class Buffer extends RetainableByteBuffer { private final RetainableByteBuffer wrapped; private final int size; private final Instant acquireInstant; private final Throwable acquireStack; private final List retainStacks = new CopyOnWriteArrayList<>(); private final List releaseStacks = new CopyOnWriteArrayList<>(); private final List overReleaseStacks = new CopyOnWriteArrayList<>(); private Buffer(RetainableByteBuffer wrapped, int size) { super(wrapped.getBuffer(), x -> {}); this.wrapped = wrapped; this.size = size; this.acquireInstant = Instant.now(); this.acquireStack = new Throwable(); } public int getSize() { return size; } public Instant getAcquireInstant() { return acquireInstant; } public Throwable getAcquireStack() { return acquireStack; } @Override protected void acquire() { wrapped.acquire(); } @Override public boolean isRetained() { return wrapped.isRetained(); } @Override public void retain() { wrapped.retain(); retainStacks.add(new Throwable()); } @Override public boolean release() { try { boolean released = wrapped.release(); if (released) { buffers.remove(this); if (LOG.isDebugEnabled()) LOG.debug("released {}", this); } releaseStacks.add(new Throwable()); return released; } catch (IllegalStateException e) { buffers.add(this); overReleaseStacks.add(new Throwable()); throw e; } } public String dump() { StringWriter w = new StringWriter(); PrintWriter pw = new PrintWriter(w); getAcquireStack().printStackTrace(pw); pw.println("\n" + retainStacks.size() + " retain(s)"); for (Throwable retainStack : retainStacks) { retainStack.printStackTrace(pw); } pw.println("\n" + releaseStacks.size() + " release(s)"); for (Throwable releaseStack : releaseStacks) { releaseStack.printStackTrace(pw); } pw.println("\n" + overReleaseStacks.size() + " over-release(s)"); for (Throwable overReleaseStack : overReleaseStacks) { overReleaseStack.printStackTrace(pw); } return String.format("%s@%x of %d bytes on %s wrapping %s acquired at %s", getClass().getSimpleName(), hashCode(), getSize(), getAcquireInstant(), wrapped, w); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy