Skip to content

Commit 5e25af8

Browse files
authored
apacheGH-15050: [Java][Docs] Update and consolidate Memory documentation (apache#15051)
* Closes: apache#15050 Authored-by: Larry White <lwhite1@users.noreply.github.com> Signed-off-by: David Li <li.davidm96@gmail.com>
1 parent 09453f9 commit 5e25af8

3 files changed

Lines changed: 199 additions & 152 deletions

File tree

‎docs/source/java/memory.rst‎

Lines changed: 197 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,27 @@
1919
Memory Management
2020
=================
2121

22-
The memory modules contain all the functionality that Arrow uses to manage memory (allocation and deallocation).
22+
The memory modules contain all the functionality that Arrow uses to allocate and deallocate memory. This document is divided in two parts:
23+
The first part, *Memory Basics*, provides a high-level introduction. The following section, *Arrow Memory In-Depth*, fills in the details.
24+
25+
.. contents::
26+
27+
Memory Basics
28+
=============
2329
This section will introduce you to the major concepts in Java’s memory management:
2430

2531
* `ArrowBuf`_
2632
* `BufferAllocator`_
2733
* Reference counting
2834

29-
.. contents::
35+
It also provides some guidelines for working with memory in Arrow, and describes how to debug memory issues when they arise.
3036

3137
Getting Started
32-
===============
38+
---------------
3339

3440
Arrow's memory management is built around the needs of the columnar format and using off-heap memory.
35-
Also, it is its own independent implementation, and does not wrap the C++ implementation.
41+
Arrow Java has its own independent implementation. It does not wrap the C++ implementation, although the framework is flexible enough
42+
to be used with memory allocated in C++ that is used by Java code.
3643

3744
Arrow provides multiple modules: the core interfaces, and implementations of the interfaces.
3845
Users need the core interfaces, and exactly one of the implementations.
@@ -41,25 +48,29 @@ Users need the core interfaces, and exactly one of the implementations.
4148
* ``memory-netty``: An implementation of the memory interfaces based on the `Netty`_ library.
4249
* ``memory-unsafe``: An implementation of the memory interfaces based on the `sun.misc.Unsafe`_ library.
4350

51+
4452
ArrowBuf
45-
========
53+
--------
4654

4755
ArrowBuf represents a single, contiguous region of `direct memory`_. It consists of an address and a length,
4856
and provides low-level interfaces for working with the contents, similar to ByteBuffer.
4957

5058
Unlike (Direct)ByteBuffer, it has reference counting built in, as discussed later.
5159

5260
Why Arrow Uses Direct Memory
53-
----------------------------
61+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5462

5563
* The JVM can optimize I/O operations when using direct memory/direct buffers; it will attempt to avoid copying buffer contents to/from an intermediate buffer. This can speed up IPC in Arrow.
5664
* Since Arrow always uses direct memory, JNI modules can directly wrap native memory addresses instead of copying data. We use this in modules like the C Data Interface.
5765
* Conversely, on the C++ side of the JNI boundary, we can directly access the memory in ArrowBuf without copying data.
5866

5967
BufferAllocator
60-
===============
68+
---------------
6169

62-
The `BufferAllocator`_ interface deals with allocating ArrowBufs for the application.
70+
The `BufferAllocator`_ is primarily an arena or nursery used for accounting of buffers (ArrowBuf instances).
71+
As the name suggests, it can allocate new buffers associated with itself, but it can also
72+
handle the accounting for buffers allocated elsewhere. For example, it handles the Java-side accounting for
73+
memory allocated in C++ and shared with Java using the C-Data Interface. In the code below it performs an allocation:
6374

6475
.. code-block:: Java
6576
@@ -89,51 +100,54 @@ memory from a child allocator, those allocations are also reflected in all paren
89100
effectively sets the program-wide memory limit, and serves as the master bookkeeper for all memory allocations.
90101

91102
Child allocators are not strictly required, but can help better organize code. For instance, a lower memory limit can
92-
be set for a particular section of code. When the allocator is closed, it then checks that that section didn't leak any
93-
memory. And child allocators can be named, which makes it easier to tell where an ArrowBuf came from during debugging.
103+
be set for a particular section of code. The child allocator can be closed when that section completes,
104+
at which point it checks that that section didn't leak any memory.
105+
Child allocators can also be named, which makes it easier to tell where an ArrowBuf came from during debugging.
94106

95107
Reference counting
96-
==================
108+
------------------
97109

98-
Direct memory is more expensive to allocate and deallocate. That's why allocators pool or cache direct buffers.
99-
100-
Because we want to pool/cache buffers and manage them deterministically, we use manual reference counting instead of
101-
the garbage collector. This simply means that each buffer has a counter keeping track of the number of references to
110+
Because direct memory is expensive to allocate and deallocate, allocators may share direct buffers. To managed shared buffers
111+
deterministically, we use manual reference counting instead of the garbage collector.
112+
This simply means that each buffer has a counter keeping track of the number of references to
102113
the buffer, and the user is responsible for properly incrementing/decrementing the counter as the buffer is used.
103114

104-
In Arrow, each ArrowBuf has an associated `ReferenceManager`_ that tracks the reference count, which can be retrieved
105-
with ArrowBuf.getReferenceManager(). The reference count can be updated with `ReferenceManager.release`_ and
106-
`ReferenceManager.retain`_.
115+
In Arrow, each ArrowBuf has an associated `ReferenceManager`_ that tracks the reference count. You can retrieve
116+
it with ArrowBuf.getReferenceManager(). The reference count is updated using `ReferenceManager.release`_ to decrement the count,
117+
and `ReferenceManager.retain`_ to increment it.
107118

108-
Of course, this is tedious and error-prone, so usually, instead of directly working with buffers, we should use
119+
Of course, this is tedious and error-prone, so instead of directly working with buffers, we typically use
109120
higher-level APIs like ValueVector. Such classes generally implement Closeable/AutoCloseable and will automatically
110121
decrement the reference count when closed.
111122

112123
Allocators implement AutoCloseable as well. In this case, closing the allocator will check that all buffers
113124
obtained from the allocator are closed. If not, ``close()`` method will raise an exception; this helps track
114125
memory leaks from unclosed buffers.
115126

116-
As you see, reference counting needs to be handled carefully. To ensure that an
127+
Reference counting needs to be handled carefully. To ensure that an
117128
independent section of code has fully cleaned up all allocated buffers, use a new child allocator.
118129

119130
Development Guidelines
120-
======================
131+
----------------------
121132

122133
Applications should generally:
123134

124135
* Use the BufferAllocator interface in APIs instead of RootAllocator.
125136
* Create one RootAllocator at the start of the program.
126137
* ``close()`` allocators after use (whether they are child allocators or the RootAllocator), either manually or preferably via a try-with-resources statement.
127138

128-
Debugging Memory Leaks/Allocation
129-
=================================
130139

131-
Allocators have a debug mode that makes it easier to figure out where a leak is originated.
132-
To enable it, enable assertions with ``-ea`` or set the system property, ``-Darrow.memory.debug.allocator=true``.
133-
When enabled, a log will be kept of allocations.
140+
Debugging Memory Leaks/Allocation
141+
---------------------------------
134142

135-
Arrow logs some allocation information via SLF4J; configure it properly to see these logs (e.g. via Logback/Apache Log4j).
143+
In ``DEBUG`` mode, the allocator and
144+
supporting classes will record additional debug tracking information to
145+
better track down memory leaks and issues. To enable DEBUG mode, either
146+
enable Java assertions with ``-ea`` or pass the following system
147+
property to the VM when starting
148+
``-Darrow.memory.debug.allocator=true``.
136149

150+
When DEBUG is enabled, a log will be kept of allocations. Configure SLF4J to see these logs (e.g. via Logback/Apache Log4j).
137151
Consider the following example to see how it helps us with the tracking of allocators:
138152

139153
.. code-block:: Java
@@ -215,6 +229,9 @@ This will include information about allocation operations on the buffer with sta
215229
at jdk.jshell.execution.Util.forwardExecutionControlAndIO(Util.java:137)
216230
at jdk.jshell.execution.RemoteExecutionControl.main(RemoteExecutionControl.java:70)
217231
232+
The BufferAllocator also provides a ``BufferAllocator.toVerboseString()`` which can be used in
233+
``DEBUG`` mode to get extensive stacktrace information and events associated with various Allocator behaviors.
234+
218235
Finally, enabling the ``TRACE`` logging level will automatically provide this stack trace when the allocator is closed:
219236

220237
.. code-block:: java
@@ -276,6 +293,7 @@ Finally, enabling the ``TRACE`` logging level will automatically provide this st
276293
.. _`ArrowBuf`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/ArrowBuf.html
277294
.. _`ArrowBuf.print()`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/ArrowBuf.html#print-java.lang.StringBuilder-int-org.apache.arrow.memory.BaseAllocator.Verbosity-
278295
.. _`BufferAllocator`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/BufferAllocator.html
296+
.. _`BufferLedger`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/BufferLedger.html
279297
.. _`RootAllocator`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/RootAllocator.html
280298
.. _`newChildAllocator`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/RootAllocator.html#newChildAllocator-java.lang.String-org.apache.arrow.memory.AllocationListener-long-long-
281299
.. _`Netty`: https://netty.io/wiki/
@@ -284,3 +302,155 @@ Finally, enabling the ``TRACE`` logging level will automatically provide this st
284302
.. _`ReferenceManager`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/ReferenceManager.html
285303
.. _`ReferenceManager.release`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/ReferenceManager.html#release--
286304
.. _`ReferenceManager.retain`: https://arrow.apache.org/docs/java/reference/org/apache/arrow/memory/ReferenceManager.html#retain--
305+
306+
Arrow Memory In-Depth
307+
=====================
308+
309+
Design Principles
310+
-----------------
311+
Arrow’s memory model is based on the following basic concepts:
312+
313+
- Memory can be allocated up to some limit. That limit could be a real
314+
limit (OS/JVM) or a locally imposed limit.
315+
- Allocation operates in two phases: accounting then actual allocation.
316+
Allocation could fail at either point.
317+
- Allocation failure should be recoverable. In all cases, the Allocator
318+
infrastructure should expose memory allocation failures (OS or
319+
internal limit-based) as ``OutOfMemoryException``\ s.
320+
- Any allocator can reserve memory when created. This memory shall be
321+
held such that this allocator will always be able to allocate that
322+
amount of memory.
323+
- A particular application component should work to use a local
324+
allocator to understand local memory usage and better debug memory
325+
leaks.
326+
- The same physical memory can be shared by multiple allocators and the
327+
allocator must provide an accounting paradigm for this purpose.
328+
329+
Reserving Memory
330+
----------------
331+
332+
Arrow provides two different ways to reserve memory:
333+
334+
- BufferAllocator accounting reservations: When a new allocator (other
335+
than the ``RootAllocator``) is initialized, it can set aside memory
336+
that it will keep locally for its lifetime. This is memory that will
337+
never be released back to its parent allocator until the allocator is
338+
closed.
339+
- ``AllocationReservation`` via BufferAllocator.newReservation():
340+
Allows a short-term preallocation strategy so that a particular
341+
subsystem can ensure future memory is available to support a
342+
particular request.
343+
344+
Reference Counting Details
345+
--------------------------
346+
347+
Typically, the ReferenceManager implementation used is an instance of `BufferLedger`_.
348+
A BufferLedger is a ReferenceManager that also maintains the relationship between an ``AllocationManager``,
349+
a ``BufferAllocator`` and one or more individual ``ArrowBuf``\ s
350+
351+
All ArrowBufs (direct or sliced) related to a single BufferLedger/BufferAllocator combination
352+
share the same reference count and either all will be valid or all will be invalid.
353+
For simplicity of accounting, we treat that memory as being used by one
354+
of the BufferAllocators associated with the memory. When that allocator
355+
releases its claim on that memory, the memory ownership is then moved to
356+
another BufferLedger belonging to the same AllocationManager.
357+
358+
Allocation Details
359+
------------------
360+
361+
There are several Allocator types in Arrow Java:
362+
363+
- ``BufferAllocator`` - The public interface application users should be leveraging
364+
- ``BaseAllocator`` - The base implementation of memory allocation, contains the meat of the Arrow allocator implementation
365+
- ``RootAllocator`` - The root allocator. Typically only one created for a JVM. It serves as the parent/ancestor for child allocators
366+
- ``ChildAllocator`` - A child allocator that derives from the root allocator
367+
368+
Many BufferAllocators can reference the same piece of physical memory at the same
369+
time. It is the AllocationManager’s responsibility to ensure that in this situation,
370+
all memory is accurately accounted for from the Root’s perspective
371+
and also to ensure that the memory is correctly released once all
372+
BufferAllocators have stopped using that memory.
373+
374+
For simplicity of accounting, we treat that memory as being used by one
375+
of the BufferAllocators associated with the memory. When that allocator
376+
releases its claim on that memory, the memory ownership is then moved to
377+
another BufferLedger belonging to the same AllocationManager. Note that
378+
because a ArrowBuf.release() is what actually causes memory ownership
379+
transfer to occur, we always proceed with ownership transfer (even if
380+
that violates an allocator limit). It is the responsibility of the
381+
application owning a particular allocator to frequently confirm whether
382+
the allocator is over its memory limit (BufferAllocator.isOverLimit())
383+
and if so, attempt to aggressively release memory to ameliorate the
384+
situation.
385+
386+
387+
Object Hierarchy
388+
----------------
389+
390+
There are two main ways that someone can look at the object hierarchy
391+
for Arrow’s memory management scheme. The first is a memory based
392+
perspective as below:
393+
394+
Memory Perspective
395+
~~~~~~~~~~~~~~~~~~
396+
397+
.. code-block:: none
398+
399+
+ AllocationManager
400+
|
401+
|-- UnsignedDirectLittleEndian (One per AllocationManager)
402+
|
403+
|-+ BufferLedger 1 ==> Allocator A (owning)
404+
| ` - ArrowBuf 1
405+
|-+ BufferLedger 2 ==> Allocator B (non-owning)
406+
| ` - ArrowBuf 2
407+
|-+ BufferLedger 3 ==> Allocator C (non-owning)
408+
| - ArrowBuf 3
409+
| - ArrowBuf 4
410+
` - ArrowBuf 5
411+
412+
In this picture, a piece of memory is owned by an allocator manager. An
413+
allocator manager is responsible for that piece of memory no matter
414+
which allocator(s) it is working with. An allocator manager will have
415+
relationships with a piece of raw memory (via its reference to
416+
UnsignedDirectLittleEndian) as well as references to each
417+
BufferAllocator it has a relationship to.
418+
419+
Allocator Perspective
420+
~~~~~~~~~~~~~~~~~~~~~
421+
422+
.. code-block:: none
423+
424+
+ RootAllocator
425+
|-+ ChildAllocator 1
426+
| | - ChildAllocator 1.1
427+
| ` ...
428+
|
429+
|-+ ChildAllocator 2
430+
|-+ ChildAllocator 3
431+
| |
432+
| |-+ BufferLedger 1 ==> AllocationManager 1 (owning) ==> UDLE
433+
| | `- ArrowBuf 1
434+
| `-+ BufferLedger 2 ==> AllocationManager 2 (non-owning)==> UDLE
435+
| `- ArrowBuf 2
436+
|
437+
|-+ BufferLedger 3 ==> AllocationManager 1 (non-owning)==> UDLE
438+
| ` - ArrowBuf 3
439+
|-+ BufferLedger 4 ==> AllocationManager 2 (owning) ==> UDLE
440+
| - ArrowBuf 4
441+
| - ArrowBuf 5
442+
` - ArrowBuf 6
443+
444+
In this picture, a RootAllocator owns three ChildAllocators. The first
445+
ChildAllocator (ChildAllocator 1) owns a subsequent ChildAllocator.
446+
ChildAllocator has two BufferLedgers/AllocationManager references.
447+
Coincidentally, each of these AllocationManager’s is also associated
448+
with the RootAllocator. In this case, one of the these
449+
AllocationManagers is owned by ChildAllocator 3 (AllocationManager 1)
450+
while the other AllocationManager (AllocationManager 2) is
451+
owned/accounted for by the RootAllocator. Note that in this scenario,
452+
ArrowBuf 1 is sharing the underlying memory as ArrowBuf 3. However the
453+
subset of that memory (e.g. through slicing) might be different. Also
454+
note that ArrowBuf 2 and ArrowBuf 4, 5 and 6 are also sharing the same
455+
underlying memory. Also note that ArrowBuf 4, 5 and 6 all share the same
456+
reference count and fate.

0 commit comments

Comments
 (0)