This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-java.git
The following commit(s) were added to refs/heads/main by this push:
new 5dfd25950 GH-1038: Trim object memory for ArrowBuf (#1044)
5dfd25950 is described below
commit 5dfd2595080a15d3d6ff3e40d8de57af4bdd7858
Author: Logan Riggs <[email protected]>
AuthorDate: Wed Mar 4 12:03:18 2026 -0800
GH-1038: Trim object memory for ArrowBuf (#1044)
## What's Changed
A significant number of ArrowBuf and BufferLedger objects are created
during certain workloads. Saving several bytes per instance could add up
to significant memory savings and reduced memory allocation expense and
garbage collection.
The id field, which was a sequential value used when logging object
information, is replaced with an identity hash code. This should still
allow enough information for debugging without the memory overhead.
There may be possible duplicate values but it shouldn't matter for
logging purposes.
Atomic fields can be replaced by a primitive and a static updater which
saves several bytes per instance.
### ArrowBuf
| Component | Before | After | Savings |
|-----------|--------|-------|---------|
| `idGenerator` (static) | `AtomicLong` | Removed | 24 bytes globally |
| `id` field (per instance) | `long` (8 bytes) | Removed | **8 bytes per
instance** |
| `getId()` | Returns `id` field | Returns
`System.identityHashCode(this)` | — |
### BufferLedger
| Component | Before | After | Savings |
|-----------|--------|-------|---------|
| `LEDGER_ID_GENERATOR` (static) | `AtomicLong` | Removed | 24 bytes
globally |
| `ledgerId` (per instance) | `long` (8 bytes) | Removed | **8 bytes per
instance** |
| `bufRefCnt` | `AtomicInteger` (24 bytes) | `volatile int` + static
updater | **20 bytes per instance** |
### Total Savings
| Scale | ArrowBuf | BufferLedger | Combined |
|-------|----------|--------------|----------|
| 100K | 800 KB | 2.8 MB | **~3.6 MB** |
| 1M | 8 MB | 28 MB | **~36 MB** |
| 10M | 80 MB | 280 MB | **~360 MB** |
### Benchmarking
I ran the added benchmark before and after the metadata trimming.
**Metadata Trimmed**
| Benchmark | Mode | Score | Error |Units|
|-------|----------|--------------|----------|----------|
|MemoryFootprintBenchmarks.measureAllocationPerformance | avgt | 456.831
|± 36.059 | us/op|
|MemoryFootprintBenchmarks.measureArrowBufMemoryFootprint | ss | 161.085
|± 35.596| ms/op|
|Created 100000 ArrowBuf instances. Heap memory used | sum | 35631520
bytes (33.98 MB) |0 |bytes|
|Average memory per ArrowBuf| sum | 356.32 bytes |0 |bytes|
**Previous Object Layout**
| Benchmark | Mode | Score | Error |Units|
|-------|----------|--------------|----------|----------|
|MemoryFootprintBenchmarks.measureAllocationPerformance | avgt | 466.171
|± 16.233 | us/op|
|MemoryFootprintBenchmarks.measureArrowBufMemoryFootprint | ss | 176.790
|± 17.943 |ms/op|
|Created 100000 ArrowBuf instances. Heap memory used | sum | 38817480
bytes (37.02 MB) |0 |bytes|
|Average memory per ArrowBuf| sum | 388.17 bytes |0 |bytes|
Closes #1038.
---
.../java/org/apache/arrow/memory/Accountant.java | 42 ++--
.../java/org/apache/arrow/memory/ArrowBuf.java | 19 +-
.../java/org/apache/arrow/memory/BufferLedger.java | 28 +--
.../arrow/memory/MemoryFootprintBenchmarks.java | 213 +++++++++++++++++++++
4 files changed, 263 insertions(+), 39 deletions(-)
diff --git
a/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
b/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
index 5d052c2cd..d4d76f57f 100644
--- a/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
+++ b/memory/memory-core/src/main/java/org/apache/arrow/memory/Accountant.java
@@ -16,7 +16,7 @@
*/
package org.apache.arrow.memory;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import org.apache.arrow.util.Preconditions;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -37,16 +37,24 @@ class Accountant implements AutoCloseable {
*/
protected final long reservation;
- private final AtomicLong peakAllocation = new AtomicLong();
+ // AtomicLongFieldUpdaters for memory accounting fields to reduce memory
overhead
+ private static final AtomicLongFieldUpdater<Accountant>
PEAK_ALLOCATION_UPDATER =
+ AtomicLongFieldUpdater.newUpdater(Accountant.class, "peakAllocation");
+ private static final AtomicLongFieldUpdater<Accountant>
ALLOCATION_LIMIT_UPDATER =
+ AtomicLongFieldUpdater.newUpdater(Accountant.class, "allocationLimit");
+ private static final AtomicLongFieldUpdater<Accountant>
LOCALLY_HELD_MEMORY_UPDATER =
+ AtomicLongFieldUpdater.newUpdater(Accountant.class, "locallyHeldMemory");
+
+ private volatile long peakAllocation = 0;
/**
* Maximum local memory that can be held. This can be externally updated.
Changing it won't cause
* past memory to change but will change responses to future allocation
efforts
*/
- private final AtomicLong allocationLimit = new AtomicLong();
+ private volatile long allocationLimit = 0;
/** Currently allocated amount of memory. */
- private final AtomicLong locallyHeldMemory = new AtomicLong();
+ private volatile long locallyHeldMemory = 0;
public Accountant(
@Nullable Accountant parent, String name, long reservation, long
maxAllocation) {
@@ -64,7 +72,7 @@ class Accountant implements AutoCloseable {
this.parent = parent;
this.name = name;
this.reservation = reservation;
- this.allocationLimit.set(maxAllocation);
+ ALLOCATION_LIMIT_UPDATER.set(this, maxAllocation);
if (reservation != 0) {
Preconditions.checkArgument(parent != null, "parent must not be null");
@@ -117,12 +125,12 @@ class Accountant implements AutoCloseable {
}
private void updatePeak() {
- final long currentMemory = locallyHeldMemory.get();
+ final long currentMemory = locallyHeldMemory;
while (true) {
- final long previousPeak = peakAllocation.get();
+ final long previousPeak = peakAllocation;
if (currentMemory > previousPeak) {
- if (!peakAllocation.compareAndSet(previousPeak, currentMemory)) {
+ if (!PEAK_ALLOCATION_UPDATER.compareAndSet(this, previousPeak,
currentMemory)) {
// peak allocation changed underneath us. try again.
continue;
}
@@ -166,7 +174,7 @@ class Accountant implements AutoCloseable {
final boolean incomingUpdatePeak,
final boolean forceAllocation,
@Nullable AllocationOutcomeDetails details) {
- final long oldLocal = locallyHeldMemory.getAndAdd(size);
+ final long oldLocal = LOCALLY_HELD_MEMORY_UPDATER.getAndAdd(this, size);
final long newLocal = oldLocal + size;
// Borrowed from Math.addExact (but avoid exception here)
// Overflow if result has opposite sign of both arguments
@@ -174,7 +182,7 @@ class Accountant implements AutoCloseable {
// failure
final boolean overflow = ((oldLocal ^ newLocal) & (size ^ newLocal)) < 0;
final long beyondReservation = newLocal - reservation;
- final boolean beyondLimit = overflow || newLocal > allocationLimit.get();
+ final boolean beyondLimit = overflow || newLocal > allocationLimit;
final boolean updatePeak = forceAllocation || (incomingUpdatePeak &&
!beyondLimit);
if (details != null) {
@@ -214,7 +222,7 @@ class Accountant implements AutoCloseable {
public void releaseBytes(long size) {
// reduce local memory. all memory released above reservation should be
released up the tree.
- final long newSize = locallyHeldMemory.addAndGet(-size);
+ final long newSize = LOCALLY_HELD_MEMORY_UPDATER.addAndGet(this, -size);
Preconditions.checkArgument(newSize >= 0, "Accounted size went negative.");
@@ -255,7 +263,7 @@ class Accountant implements AutoCloseable {
* @return Limit in bytes.
*/
public long getLimit() {
- return allocationLimit.get();
+ return allocationLimit;
}
/**
@@ -274,7 +282,7 @@ class Accountant implements AutoCloseable {
* @param newLimit The limit in bytes.
*/
public void setLimit(long newLimit) {
- allocationLimit.set(newLimit);
+ ALLOCATION_LIMIT_UPDATER.set(this, newLimit);
}
/**
@@ -284,7 +292,7 @@ class Accountant implements AutoCloseable {
* @return Currently allocate memory in bytes.
*/
public long getAllocatedMemory() {
- return locallyHeldMemory.get();
+ return locallyHeldMemory;
}
/**
@@ -293,17 +301,17 @@ class Accountant implements AutoCloseable {
* @return The peak allocated memory in bytes.
*/
public long getPeakMemoryAllocation() {
- return peakAllocation.get();
+ return peakAllocation;
}
public long getHeadroom() {
- long localHeadroom = allocationLimit.get() - locallyHeldMemory.get();
+ long localHeadroom = allocationLimit - locallyHeldMemory;
if (parent == null) {
return localHeadroom;
}
// Amount of reserved memory left on top of what parent has
- long reservedHeadroom = Math.max(0, reservation - locallyHeldMemory.get());
+ long reservedHeadroom = Math.max(0, reservation - locallyHeldMemory);
return Math.min(localHeadroom, parent.getHeadroom() + reservedHeadroom);
}
}
diff --git
a/memory/memory-core/src/main/java/org/apache/arrow/memory/ArrowBuf.java
b/memory/memory-core/src/main/java/org/apache/arrow/memory/ArrowBuf.java
index b8012fe64..9712be34d 100644
--- a/memory/memory-core/src/main/java/org/apache/arrow/memory/ArrowBuf.java
+++ b/memory/memory-core/src/main/java/org/apache/arrow/memory/ArrowBuf.java
@@ -24,7 +24,6 @@ import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ReadOnlyBufferException;
-import java.util.concurrent.atomic.AtomicLong;
import org.apache.arrow.memory.BaseAllocator.Verbosity;
import org.apache.arrow.memory.util.CommonUtil;
import org.apache.arrow.memory.util.HistoricalLog;
@@ -57,9 +56,8 @@ public final class ArrowBuf implements AutoCloseable {
private static final int DOUBLE_SIZE = Double.BYTES;
private static final int LONG_SIZE = Long.BYTES;
- private static final AtomicLong idGenerator = new AtomicLong(0);
private static final int LOG_BYTES_PER_ROW = 10;
- private final long id = idGenerator.incrementAndGet();
+
private final ReferenceManager referenceManager;
private final @Nullable BufferManager bufferManager;
private final long addr;
@@ -67,7 +65,8 @@ public final class ArrowBuf implements AutoCloseable {
private long writerIndex;
private final @Nullable HistoricalLog historicalLog =
BaseAllocator.DEBUG
- ? new HistoricalLog(BaseAllocator.DEBUG_LOG_LENGTH, "ArrowBuf[%d]",
id)
+ ? new HistoricalLog(
+ BaseAllocator.DEBUG_LOG_LENGTH, "ArrowBuf[%d]",
System.identityHashCode(this))
: null;
private volatile long capacity;
@@ -218,7 +217,8 @@ public final class ArrowBuf implements AutoCloseable {
@Override
public String toString() {
- return String.format("ArrowBuf[%d], address:%d, capacity:%d", id,
memoryAddress(), capacity);
+ return String.format(
+ "ArrowBuf[%d], address:%d, capacity:%d", getId(), memoryAddress(),
capacity);
}
@Override
@@ -1080,12 +1080,15 @@ public final class ArrowBuf implements AutoCloseable {
}
/**
- * Get the integer id assigned to this ArrowBuf for debugging purposes.
+ * Get the id assigned to this ArrowBuf for debugging purposes.
+ *
+ * <p>Returns {@link System#identityHashCode(Object)} which provides a
unique identifier for this
+ * buffer without any per-instance memory overhead.
*
- * @return integer id
+ * @return the identity hash code for this buffer
*/
public long getId() {
- return id;
+ return System.identityHashCode(this);
}
/**
diff --git
a/memory/memory-core/src/main/java/org/apache/arrow/memory/BufferLedger.java
b/memory/memory-core/src/main/java/org/apache/arrow/memory/BufferLedger.java
index b562a421e..eb90efcbb 100644
--- a/memory/memory-core/src/main/java/org/apache/arrow/memory/BufferLedger.java
+++ b/memory/memory-core/src/main/java/org/apache/arrow/memory/BufferLedger.java
@@ -17,8 +17,7 @@
package org.apache.arrow.memory;
import java.util.IdentityHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import org.apache.arrow.memory.util.CommonUtil;
import org.apache.arrow.memory.util.HistoricalLog;
import org.apache.arrow.util.Preconditions;
@@ -32,12 +31,13 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class BufferLedger implements ValueWithKeyIncluded<BufferAllocator>,
ReferenceManager {
private final @Nullable IdentityHashMap<ArrowBuf, @Nullable Object> buffers =
BaseAllocator.DEBUG ? new IdentityHashMap<>() : null;
- private static final AtomicLong LEDGER_ID_GENERATOR = new AtomicLong(0);
- // unique ID assigned to each ledger
- private final long ledgerId = LEDGER_ID_GENERATOR.incrementAndGet();
- private final AtomicInteger bufRefCnt = new AtomicInteger(0); // start at
zero so we can
- // manage request for retain
- // correctly
+
+ // AtomicIntegerFieldUpdater for bufRefCnt to reduce memory overhead
+ private static final AtomicIntegerFieldUpdater<BufferLedger>
BUF_REF_CNT_UPDATER =
+ AtomicIntegerFieldUpdater.newUpdater(BufferLedger.class, "bufRefCnt");
+ // start at zero so we can manage request for retain correctly
+ private volatile int bufRefCnt = 0;
+
private final long lCreationTime = System.nanoTime();
private final BufferAllocator allocator;
private final AllocationManager allocationManager;
@@ -78,7 +78,7 @@ public class BufferLedger implements
ValueWithKeyIncluded<BufferAllocator>, Refe
*/
@Override
public int getRefCount() {
- return bufRefCnt.get();
+ return bufRefCnt;
}
/**
@@ -86,7 +86,7 @@ public class BufferLedger implements
ValueWithKeyIncluded<BufferAllocator>, Refe
* ArrowBufs managed by this ledger will share the ref count.
*/
void increment() {
- bufRefCnt.incrementAndGet();
+ BUF_REF_CNT_UPDATER.incrementAndGet(this);
}
/**
@@ -144,7 +144,7 @@ public class BufferLedger implements
ValueWithKeyIncluded<BufferAllocator>, Refe
allocator.assertOpen();
final int outcome;
synchronized (allocationManager) {
- outcome = bufRefCnt.addAndGet(-decrement);
+ outcome = BUF_REF_CNT_UPDATER.addAndGet(this, -decrement);
if (outcome == 0) {
lDestructionTime = System.nanoTime();
// refcount of this reference manager has dropped to 0
@@ -174,7 +174,7 @@ public class BufferLedger implements
ValueWithKeyIncluded<BufferAllocator>, Refe
if (historicalLog != null) {
historicalLog.recordEvent("retain(%d)", increment);
}
- final int originalReferenceCount = bufRefCnt.getAndAdd(increment);
+ final int originalReferenceCount = BUF_REF_CNT_UPDATER.getAndAdd(this,
increment);
Preconditions.checkArgument(originalReferenceCount > 0);
}
@@ -472,13 +472,13 @@ public class BufferLedger implements
ValueWithKeyIncluded<BufferAllocator>, Refe
void print(StringBuilder sb, int indent, BaseAllocator.Verbosity verbosity) {
CommonUtil.indent(sb, indent)
.append("ledger[")
- .append(ledgerId)
+ .append(System.identityHashCode(this))
.append("] allocator: ")
.append(allocator.getName())
.append("), isOwning: ")
.append(", size: ")
.append(", references: ")
- .append(bufRefCnt.get())
+ .append(bufRefCnt)
.append(", life: ")
.append(lCreationTime)
.append("..")
diff --git
a/performance/src/main/java/org/apache/arrow/memory/MemoryFootprintBenchmarks.java
b/performance/src/main/java/org/apache/arrow/memory/MemoryFootprintBenchmarks.java
new file mode 100644
index 000000000..395ba13b9
--- /dev/null
+++
b/performance/src/main/java/org/apache/arrow/memory/MemoryFootprintBenchmarks.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.arrow.memory;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+/**
+ * Benchmarks for memory footprint of Arrow memory objects.
+ *
+ * <p>This benchmark measures the heap memory overhead of creating many
ArrowBuf instances. The
+ * optimizations using AtomicFieldUpdater instead of AtomicLong/AtomicInteger
objects should reduce
+ * memory overhead significantly.
+ *
+ * <p>Expected savings per instance: - ArrowBuf: 8 bytes (id field removed) -
BufferLedger: 28 bytes
+ * (20 from AtomicInteger + 8 from ledgerId) - Accountant: 48 bytes (3 × 16
bytes from AtomicLong
+ * objects)
+ *
+ * <p>For 1M ArrowBuf instances, this should save approximately 8 MB of heap
memory.
+ */
+@State(Scope.Benchmark)
+@Fork(
+ value = 1,
+ jvmArgs = {"-Xms2g", "-Xmx2g"})
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+public class MemoryFootprintBenchmarks {
+
+ /** Number of ArrowBuf instances to create for memory footprint measurement.
*/
+ private static final int NUM_BUFFERS = 100_000;
+
+ /** Size in bytes of each buffer allocation. */
+ private static final int BUFFER_SIZE = 1024;
+
+ /** Root allocator used for all buffer allocations in the benchmark. */
+ private RootAllocator allocator;
+
+ /** Array to hold references to allocated buffers, preventing garbage
collection. */
+ private ArrowBuf[] buffers;
+
+ /** JMX bean for querying heap memory usage statistics. */
+ private MemoryMXBean memoryBean;
+
+ /**
+ * Sets up the benchmark state before each trial.
+ *
+ * <p>Initializes the memory monitoring bean, creates a root allocator with
sufficient capacity,
+ * and allocates the buffer reference array.
+ */
+ @Setup(Level.Trial)
+ public void setup() {
+ memoryBean = ManagementFactory.getMemoryMXBean();
+ allocator = new RootAllocator((long) NUM_BUFFERS * BUFFER_SIZE);
+ buffers = new ArrowBuf[NUM_BUFFERS];
+ }
+
+ /**
+ * Cleans up buffers after each benchmark invocation.
+ *
+ * <p>Closes all allocated buffers to prevent memory leaks and ensure each
iteration starts with a
+ * clean slate. This is critical for the memory footprint benchmark which
allocates many buffers
+ * that would otherwise accumulate across warmup and measurement iterations.
+ */
+ @TearDown(Level.Invocation)
+ public void tearDown() {
+ for (int i = 0; i < NUM_BUFFERS; i++) {
+ if (buffers[i] != null) {
+ buffers[i].close();
+ buffers[i] = null;
+ }
+ }
+ }
+
+ /**
+ * Cleans up the allocator after the trial completes.
+ *
+ * <p>Closes the root allocator to release all resources after all warmup
and measurement
+ * iterations are complete.
+ */
+ @TearDown(Level.Trial)
+ public void tearDownTrial() {
+ allocator.close();
+ }
+
+ /**
+ * Benchmark that measures heap memory usage when creating many ArrowBuf
instances.
+ *
+ * <p>This benchmark creates {@value #NUM_BUFFERS} ArrowBuf instances and
measures the heap memory
+ * used. With the AtomicFieldUpdater optimizations, we expect to save
approximately 800 KB of heap
+ * memory (8 bytes × 100,000 instances) just from removing the id field in
ArrowBuf.
+ *
+ * <p>The benchmark performs garbage collection before and after allocation
to ensure accurate
+ * measurement of heap memory delta. Results are printed to stdout for
analysis.
+ *
+ * @return the total heap memory used by the allocated buffers in bytes
+ */
+ @Benchmark
+ @BenchmarkMode(Mode.SingleShotTime)
+ @OutputTimeUnit(TimeUnit.MILLISECONDS)
+ public long measureArrowBufMemoryFootprint() {
+ // Force GC before measurement
+ System.gc();
+ System.gc();
+ System.gc();
+
+ MemoryUsage heapBefore = memoryBean.getHeapMemoryUsage();
+ long usedBefore = heapBefore.getUsed();
+
+ // Allocate buffers
+ for (int i = 0; i < NUM_BUFFERS; i++) {
+ buffers[i] = allocator.buffer(BUFFER_SIZE);
+ }
+
+ // Force GC to get accurate measurement
+ System.gc();
+ System.gc();
+ System.gc();
+
+ MemoryUsage heapAfter = memoryBean.getHeapMemoryUsage();
+ long usedAfter = heapAfter.getUsed();
+
+ long memoryUsed = usedAfter - usedBefore;
+
+ // Print memory usage for analysis
+ System.out.printf(
+ "Created %d ArrowBuf instances. Heap memory used: %d bytes (%.2f
MB)%n",
+ NUM_BUFFERS, memoryUsed, memoryUsed / (1024.0 * 1024.0));
+ System.out.printf(
+ "Average memory per ArrowBuf: %.2f bytes%n", (double) memoryUsed /
NUM_BUFFERS);
+
+ return memoryUsed;
+ }
+
+ /**
+ * Benchmark that measures allocation and deallocation performance.
+ *
+ * <p>This complements the memory footprint benchmark by measuring the time
it takes to allocate
+ * and deallocate 1,000 buffers in a tight loop. This helps identify any
performance regressions
+ * introduced by memory optimizations.
+ *
+ * <p>Uses a local buffer array to avoid interference with the shared {@link
#buffers} array used
+ * by other benchmarks.
+ */
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime)
+ @OutputTimeUnit(TimeUnit.MICROSECONDS)
+ public void measureAllocationPerformance() {
+ ArrowBuf[] localBuffers = new ArrowBuf[1000];
+
+ for (int i = 0; i < 1000; i++) {
+ localBuffers[i] = allocator.buffer(BUFFER_SIZE);
+ }
+
+ for (int i = 0; i < 1000; i++) {
+ localBuffers[i].close();
+ }
+ }
+
+ /**
+ * Main entry point for running the benchmarks standalone.
+ *
+ * <p>This allows running the benchmarks directly from the command line or
IDE without using the
+ * Maven JMH plugin. Example usage:
+ *
+ * <pre>{@code
+ * java -cp target/benchmarks.jar
org.apache.arrow.memory.MemoryFootprintBenchmarks
+ * }</pre>
+ *
+ * @param args command line arguments (not used)
+ * @throws RunnerException if the benchmark runner encounters an error
+ */
+ public static void main(String[] args) throws RunnerException {
+ Options opt =
+ new OptionsBuilder()
+ .include(MemoryFootprintBenchmarks.class.getSimpleName())
+ .forks(1)
+ .build();
+
+ new Runner(opt).run();
+ }
+}