2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
18 package org
.apache
.arrow
.memory
;
20 import java
.util
.concurrent
.atomic
.AtomicLong
;
22 import org
.apache
.arrow
.util
.Preconditions
;
25 * The abstract base class of AllocationManager.
27 * <p>Manages the relationship between one or more allocators and a particular UDLE. Ensures that
28 * one allocator owns the
29 * memory that multiple allocators may be referencing. Manages a BufferLedger between each of its
30 * associated allocators.
32 * <p>The only reason that this isn't package private is we're forced to put ArrowBuf in Netty's
33 * package which need access
34 * to these objects or methods.
36 * <p>Threading: AllocationManager manages thread-safety internally. Operations within the context
37 * of a single BufferLedger
38 * are lockless in nature and can be leveraged by multiple threads. Operations that cross the
39 * context of two ledgers
40 * will acquire a lock on the AllocationManager instance. Important note, there is one
41 * AllocationManager per
42 * UnsafeDirectLittleEndian buffer allocation. As such, there will be thousands of these in a
44 * contention of acquiring a lock on AllocationManager should be very low.
46 public abstract class AllocationManager
{
48 private static final AtomicLong MANAGER_ID_GENERATOR
= new AtomicLong(0);
50 private final BufferAllocator root
;
51 private final long allocatorManagerId
= MANAGER_ID_GENERATOR
.incrementAndGet();
52 // ARROW-1627 Trying to minimize memory overhead caused by previously used IdentityHashMap
53 // see JIRA for details
54 private final LowCostIdentityHashMap
<BufferAllocator
, BufferLedger
> map
= new LowCostIdentityHashMap
<>();
55 private final long amCreationTime
= System
.nanoTime();
57 // The ReferenceManager created at the time of creation of this AllocationManager
58 // is treated as the owning reference manager for the underlying chunk of memory
59 // managed by this allocation manager
60 private volatile BufferLedger owningLedger
;
61 private volatile long amDestructionTime
= 0;
63 protected AllocationManager(BufferAllocator accountingAllocator
) {
64 Preconditions
.checkNotNull(accountingAllocator
);
65 accountingAllocator
.assertOpen();
67 this.root
= accountingAllocator
.getRoot();
69 // we do a no retain association since our creator will want to retrieve the newly created
70 // ledger and will create a reference count at that point
71 this.owningLedger
= associate(accountingAllocator
, false);
74 BufferLedger
getOwningLedger() {
78 void setOwningLedger(final BufferLedger ledger
) {
79 this.owningLedger
= ledger
;
83 * Associate the existing underlying buffer with a new allocator. This will increase the
84 * reference count on the corresponding buffer ledger by 1
86 * @param allocator The target allocator to associate this buffer with.
87 * @return The reference manager (new or existing) that associates the underlying
88 * buffer to this new ledger.
90 BufferLedger
associate(final BufferAllocator allocator
) {
91 return associate(allocator
, true);
94 private BufferLedger
associate(final BufferAllocator allocator
, final boolean retain
) {
95 allocator
.assertOpen();
96 Preconditions
.checkState(root
== allocator
.getRoot(),
97 "A buffer can only be associated between two allocators that share the same root");
100 BufferLedger ledger
= map
.get(allocator
);
101 if (ledger
!= null) {
103 // bump the ref count for the ledger
109 ledger
= new BufferLedger(allocator
, this);
112 // the new reference manager will have a ref count of 1
116 // store the mapping for <allocator, reference manager>
117 BufferLedger oldLedger
= map
.put(ledger
);
118 Preconditions
.checkState(oldLedger
== null,
119 "Detected inconsistent state: A reference manager already exists for this allocator");
121 if (allocator
instanceof BaseAllocator
) {
122 // needed for debugging only: keep a pointer to reference manager inside allocator
123 // to dump state, verify allocator state etc
124 ((BaseAllocator
) allocator
).associateLedger(ledger
);
131 * The way that a particular ReferenceManager (BufferLedger) communicates back to the
132 * AllocationManager that it no longer needs to hold a reference to a particular
133 * piece of memory. Reference manager needs to hold a lock to invoke this method
134 * It is called when the shared refcount of all the ArrowBufs managed by the
135 * calling ReferenceManager drops to 0.
137 void release(final BufferLedger ledger
) {
138 final BufferAllocator allocator
= ledger
.getAllocator();
139 allocator
.assertOpen();
141 // remove the <BaseAllocator, BufferLedger> mapping for the allocator
142 // of calling BufferLedger
143 Preconditions
.checkState(map
.containsKey(allocator
),
144 "Expecting a mapping for allocator and reference manager");
145 final BufferLedger oldLedger
= map
.remove(allocator
);
147 BufferAllocator oldAllocator
= oldLedger
.getAllocator();
148 if (oldAllocator
instanceof BaseAllocator
) {
149 // needed for debug only: tell the allocator that AllocationManager is removing a
150 // reference manager associated with this particular allocator
151 ((BaseAllocator
) oldAllocator
).dissociateLedger(oldLedger
);
154 if (oldLedger
== owningLedger
) {
155 // the release call was made by the owning reference manager
157 // the only <allocator, reference manager> mapping was for the owner
158 // which now has been removed, it implies we can safely destroy the
159 // underlying memory chunk as it is no longer being referenced
160 oldAllocator
.releaseBytes(getSize());
161 // free the memory chunk associated with the allocation manager
163 oldAllocator
.getListener().onRelease(getSize());
164 amDestructionTime
= System
.nanoTime();
167 // since the refcount dropped to 0 for the owning reference manager and allocation
168 // manager will no longer keep a mapping for it, we need to change the owning
169 // reference manager to whatever the next available <allocator, reference manager>
171 BufferLedger newOwningLedger
= map
.getNextValue();
172 // we'll forcefully transfer the ownership and not worry about whether we
173 // exceeded the limit since this consumer can't do anything with this.
174 oldLedger
.transferBalance(newOwningLedger
);
177 // the release call was made by a non-owning reference manager, so after remove there have
178 // to be 1 or more <allocator, reference manager> mappings
179 Preconditions
.checkState(map
.size() > 0,
180 "The final removal of reference manager should be connected to owning reference manager");
185 * Return the size of underlying chunk of memory managed by this Allocation Manager.
187 * <p>The underlying memory chunk managed can be different from the original requested size.
189 * @return size of underlying memory chunk
191 public abstract long getSize();
194 * Return the absolute memory address pointing to the fist byte of underlying memory chunk.
196 protected abstract long memoryAddress();
199 * Release the underlying memory chunk.
201 protected abstract void release0();
204 * A factory interface for creating {@link AllocationManager}.
205 * One may extend this interface to use a user-defined AllocationManager implementation.
207 public interface Factory
{
210 * Create an {@link AllocationManager}.
212 * @param accountingAllocator The allocator that are expected to be associated with newly created AllocationManager.
213 * Currently it is always equivalent to "this"
214 * @param size Size (in bytes) of memory managed by the AllocationManager
215 * @return The created AllocationManager used by this allocator
217 AllocationManager
create(BufferAllocator accountingAllocator
, long size
);