Hi,

I would like to discuss on the potential of introducing a GC-based reference 
management strategy to Arrow Java, and we
have already been working on an implementation in our own project. I have put 
the related codes in following branch and
if it makes sense to upstream Apache Arrow I can open a PR for it:


https://github.com/zhztheplayer/arrow-1/commits/wip-chunk-cleaner

In the branch, regarding Commit 1, Commit 2 and Commit 3:

Commit 1: To break AllocationManager to two components: MemoryChunkManager[1] 
which maintains BufferLedger-
BufferAllocator mappings, and MemoryChunkAllocator[2] which performs the 
underlying allocate/destory operations. The
previous customizations such as NettyAllocationManager, 
UnsafeAllocationManager, are moved to MemoryChunkAllocator API,
as NettyMemoryChunkAllocator and UnsafeMemoryChunkAllocator.

Commit 2: To introduce a new implementation of MemoryChunkManager, 
MemoryChunkCleaner[3]. By default, MemoryChunkCleaner
converts all managed chunks to WeakReferences, and a global thread will observe 
on the assigned reference queue to
release the unused garbage chunks which are enqueued by JVM GC. Some modes[4] 
are there to provide different strategies,
such as to disable manual reference management (default), or to enable manual 
and GC-based reference management at the
same time, or to report leaks only.

Commit 3: Add API "ArrowBuf buffer(MemoryChunk chunk)" to BufferAllocator. This 
makes some special workloads, e.g. 
cross-JNI buffer sharing much easier as we will no longer need an Allocator 
directly binding to the shared memory
chunks, and will still be able to leverage all of Allocator's advantages to 
manage the chunks (E.g. to use
MemoryChunkCleaner).

Some possible Q&As about this implementation:

1. Why adding MemoryChunkAllocator, rather than just customizing 
AllocationManager?
It is to reuse all of current underlying chunk allocation strategies, Netty, 
Unsafe, and others. Also, with the layer of
MemoryChunk separated from AllocationManager we will be able to create 
MemoryChunks actively in some special cases, e.g.
cross-JNI buffer sharing, C data interface buffer importing, then populate the 
chunks to a manager BufferAllocator.

2. Why using WeakReference rather than PhantomReference?
In Java 8, PhantomReference has some issue against its referent object. The 
object cannot be garbage collected before
being enqueued. In WeakReference we don't have this issue. See ref[5].

3. Why not sun.misc.Cleaner?
Cleaner API maintains a global doubly linked list to keep the Cleaner instances 
alive. This brings overheads to us since
we will create local doubly linked list for cleaning up all the buffers on 
Allocator close. See a unit test[6].

4. Why closing all buffers on Allocator close?
This behavior can be customizabale within Mode[7]s. A principle is, when 
relying on GC, we should allow closing buffers
manually, or at least closing all of them on Allocator close. Or the actual 
release time of the underlying chunks will
be unpredictable.

5. Can we use heap based buffers?
If I am not wrong, no. The heap objects can be physically moved around by JVM. 
The addresses can vary.

6. When should GC happen?
A new AllocationListener implementation, GCTriger[8] is introduced. GC will be 
performed when BufferAllocator is full.

7. Performance?
Based on previous measurement, it doesn't bring overheads on the legacy path 
(by using default MemoryChunkManager). For
GC-based path (MemoryChunkCleaner + Mode.GC_ONLY), allocations can be 
comparatively slower due to higher GC pressure
(For example, in out Arrow-based query engine, to run TPC-DS SF1000, the 
overhead can be up to 3%). I can collect more
benchmark results in future, maybe under a JIRA ticket or a PR.

8. Breaking changes?
It doesn't break library-based allocation strategy selection like directly 
including arrow-memory-unsafe.jar to
projects. However it breaks use of the previous AllocationManager.Factory API. 
Users should migrate to
MemoryChunkAllocator API. Which in my opinion is simple to do.

The implementation is still evolving, so if the links get out-of-date you can 
check on the newest branch head. Any
suggestions welcome.

Some other discussions that may be related to this topic:

https://lists.apache.org/thread.html/r09b2e7dd8e342818190c332ee20d97f68816241d0c683e17c4f35dc6%40%3Cdev.arrow.apache.org%3E

https://lists.apache.org/thread.html/rc956b16b7ed9768cce8eaecafdc363f7b3808dafe6edf1940b139ecb%40%3Cuser.arrow.apache.org%3E

Best,
Hongze



[1] 
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/main/java/org/apache/arrow/memory/
MemoryChunkManager.java
[2]
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/main/java/org/apache/arrow/memory/MemoryChunkAllocator.java

[3]
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/main/java/org/apache/arrow/memory/MemoryChunkCleaner.java
[4]
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/main/java/org/apache/arrow/memory/MemoryChunkCleaner.java#L503
[5]
https://stackoverflow.com/questions/56684150/why-since-java-9-phantomreference-java-doc-states-that-it-is-dedicated-to-the-po
[6]
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/test/java/org/apache/arrow/memory/TestMemoryChunkCleaner.java#L204
[7]
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/main/java/org/apache/arrow/memory/MemoryChunkCleaner.java#L503
[8]
https://github.com/zhztheplayer/arrow-1/blob/52e5def7a239164b5c13457c8be38e227bb2907d/java/memory/memory-core/src/main/java/org/apache/arrow/memory/MemoryChunkCleaner.java#L402


Reply via email to