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

org.neo4j.memory.LocalMemoryTracker Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.memory;

import static java.lang.Math.max;
import static java.util.Objects.requireNonNull;
import static org.neo4j.kernel.api.exceptions.Status.General.TransactionOutOfMemoryError;
import static org.neo4j.memory.MemoryPools.NO_TRACKING;
import static org.neo4j.util.Preconditions.requireNonNegative;
import static org.neo4j.util.Preconditions.requirePositive;

import java.util.function.BooleanSupplier;

/**
 * Memory allocation tracker that can be used in local context that required tracking of memory that is independent from global. You can impose a limit on the
 * total number of allocated bytes.
 * 

* To reduce contention on the parent tracker, locally reserved bytes are batched from the parent to a local pool. Once the pool is used up, new bytes will be * reserved. Calling {@link #reset()} will give back all the reserved bytes to the parent. Forgetting to call this will "leak" bytes and starve the database of * allocations. */ public class LocalMemoryTracker implements LimitedMemoryTracker { public static final Monitor NO_MONITOR = allocatedBytesNative -> {}; public static final long NO_LIMIT = 0; private static final long INFINITY = Long.MAX_VALUE; private static final long DEFAULT_GRAB_SIZE = 1024; /** * Imposes limits on a {@link MemoryGroup} level, e.g. global maximum transactions size */ private final MemoryPool memoryPool; /** * The chunk size to reserve from the memory pool */ private final long grabSize; /** * Name of the setting that imposes the limit. */ private final String limitSettingName; private final Monitor monitor; /** * Check if current memory tracker is open and any operations are allowed */ private final BooleanSupplier openCheck; /** * A per tracker limit. */ private long localBytesLimit; /** * Number of bytes we are allowed to use on the heap. If this run out, we need to reserve more from the parent. */ private long localHeapPool; /** * The current size of the tracked heap */ private long allocatedBytesHeap; /** * The currently allocated off heap */ private long allocatedBytesNative; /** * The heap high water mark, i.e. the maximum observed allocated heap bytes */ private long heapHighWaterMark; public LocalMemoryTracker() { this(NO_TRACKING); } public LocalMemoryTracker(MemoryPool memoryPool) { this(memoryPool, INFINITY, DEFAULT_GRAB_SIZE, null); } public LocalMemoryTracker(MemoryPool memoryPool, long localBytesLimit, long grabSize, String limitSettingName) { this(memoryPool, localBytesLimit, grabSize, limitSettingName, () -> true, NO_MONITOR); } public LocalMemoryTracker( MemoryPool memoryPool, long localBytesLimit, long grabSize, String limitSettingName, BooleanSupplier openCheck, Monitor monitor) { this.memoryPool = requireNonNull(memoryPool); this.localBytesLimit = validateLimit(localBytesLimit); this.grabSize = requireNonNegative(grabSize); this.limitSettingName = limitSettingName; this.openCheck = openCheck; this.monitor = monitor; } @Override public void allocateNative(long bytes) { assert openCheck.getAsBoolean() : "Tracker should be open to allow new allocations."; if (bytes == 0) { return; } requirePositive(bytes); this.allocatedBytesNative += bytes; if (allocatedBytesHeap + allocatedBytesNative > localBytesLimit) { allocatedBytesNative -= bytes; throw new MemoryLimitExceededException( bytes, localBytesLimit, allocatedBytesHeap + allocatedBytesNative, TransactionOutOfMemoryError, limitSettingName); } try { this.memoryPool.reserveNative(bytes); } catch (MemoryLimitExceededException t) { allocatedBytesNative -= bytes; throw t; } } @Override public void releaseNative(long bytes) { assert allocatedBytesNative >= bytes : "Can't release more than it was allocated. Allocated native: " + allocatedBytesNative + ", release request: " + bytes; this.allocatedBytesNative -= bytes; this.memoryPool.releaseNative(bytes); } @Override public void allocateHeap(long bytes) { if (bytes == 0) { return; } requirePositive(bytes); assert openCheck.getAsBoolean() : "Tracker should be open to allow new allocations."; allocatedBytesHeap += bytes; if (allocatedBytesHeap + allocatedBytesNative > localBytesLimit) { allocatedBytesHeap -= bytes; throw new MemoryLimitExceededException( bytes, localBytesLimit, allocatedBytesHeap + allocatedBytesNative, TransactionOutOfMemoryError, limitSettingName); } if (allocatedBytesHeap > heapHighWaterMark) { heapHighWaterMark = allocatedBytesHeap; } if (allocatedBytesHeap > localHeapPool) { long grab = max(bytes, grabSize); try { reserveHeapFromPool(grab); } catch (MemoryLimitExceededException t) { allocatedBytesHeap -= bytes; throw t; } } } @Override public void releaseHeap(long bytes) { requireNonNegative(bytes); assert allocatedBytesHeap >= bytes : "Can't release more than it was allocated. Allocated heap: " + allocatedBytesHeap + ", release request: " + bytes; allocatedBytesHeap -= bytes; // If the localHeapPool has reserved a lot more memory than is being used release part of it again. if (localHeapPool > grabSize && (localHeapPool / 2) > allocatedBytesHeap) { long memoryToRelease = localHeapPool / 4; memoryPool.releaseHeap(memoryToRelease); localHeapPool -= memoryToRelease; } } @Override public long heapHighWaterMark() { return heapHighWaterMark; } /** * @return number of used bytes. */ @Override public long usedNativeMemory() { return allocatedBytesNative; } @Override public long estimatedHeapMemory() { return allocatedBytesHeap; } @Override public void reset() { try { checkAllocatedNativeBytes(); } finally { memoryPool.releaseHeap(localHeapPool); localHeapPool = 0; allocatedBytesHeap = 0; allocatedBytesNative = 0; heapHighWaterMark = 0; } } public void checkAllocatedNativeBytes() { if (allocatedBytesNative != 0) { monitor.potentialNativeMemoryLeak(allocatedBytesNative); } assert allocatedBytesNative == 0 : "Potential direct memory leak. Expecting all allocated direct memory to be released, but still has " + allocatedBytesNative; } @Override public MemoryTracker getScopedMemoryTracker() { return new DefaultScopedMemoryTracker(this); } @Override public void setLimit(long localBytesLimit) { this.localBytesLimit = validateLimit(localBytesLimit); } /** * Will reserve heap in the provided pool. * * @param size heap space to reserve for the local pool * @throws MemoryLimitExceededException if not enough free memory */ private void reserveHeapFromPool(long size) { memoryPool.reserveHeap(size); localHeapPool += size; } private static long validateLimit(long localBytesLimit) { return localBytesLimit == NO_LIMIT ? INFINITY : requireNonNegative(localBytesLimit); } public interface Monitor { void potentialNativeMemoryLeak(long leakedNativeMemoryBytes); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy