[PATCH] RFR Bug-pending: Enable Hotspot to Track Native Memory Usage for Direct Byte Buffers

Adam Farley8 adam.farley at uk.ibm.com
Fri Apr 13 14:14:49 UTC 2018


Hi Alan, Peter,

I see that native memory is tracked in java.nio.Bits, but that only 
includes what the user thinks they are allocating.

When the VM adds extra memory to the allocation amount this extra bit is 
not represented in the Bits total. 
A cursory glance shows, minimum, that we round the requested memory 
quantity up to the heap word size in 
the Unsafe.allocateMemory code, and something to do with nmt_header_size 
in os:malloc() (os.cpp) too.

Here's a set of solutions for the problem of "what you see isn't what you 
get" for DBBs (3 is my favourite, but I 
suspect 2 is the most likely to be accepted):

1) Use the code below to call DBB-specific Unsafe methods, allowing us to 
track the quantity of native
memory reserved for DBBs at the point of allocation.

Pros: 
- Doesn't require changing the Bits class.
- Allows us to easily devise a list of DBB native memory allocation 
locations.
- Fits with existing OpenJ9 code, which tracks DBB memory usage *after* 
native code has added to the total 
memory requested, resulting in more accurate totals than Bits.

Cons: 
- Requires some work on the Hotspot side to make it happen if Hotspot 
users want the true benefit.
OR
- Requires a commit that only benefits Java if you're running on the 
OpenJ9 VM.

2) Modify the Bits code to interrogate the VM via an extra method in 
Unsafe, in order to determine the
true quantity of native memory that is being allocated. 

E.g. User requests 10 bits, VM says it needs +2, add 12 to cumulative 
total, return "true". 
User later says to free 10 bits, VM says it needs +2, so we subtract 12 
from the total.

Note: For consistency, the VM and Bits should use the same code when 
figuring out how much space 
will be added to the request by the VM.

Pros: 
- Much smaller change than (1)
- Makes Bits much more accurate.
- Retains the Bits interface, and doesn't require a change to Unsafe.

Cons: 
- Requires us to add a native method to Bits, or somewhere visible to 
Bits.

3) Modify the Bits code to use an internal array for memory reservations, 
returns an index. The user
can use this index to:
- Return the amount of memory they requested.
- Return the amount of memory the VM will actually reserve (Bits will 
retrieve this from the VM via a 
native method).
- Set the address the VM gave them for this reservation. 
- Free the memory reserved.

Note: The existing internal totals, along with Shared Secrets, remain.

Pros: 
- *Much* more accurate memory tracking for DBBs.
- Easier debugging for DBB overruns, as we know where all the DBBs are.
- Prevents the user trying to free too much or too little memory for an 
allocated DBB.

Cons: 
- Many changes to Bits.
- Native code changes.
- Probably best to restructure the memory allocation code to share the 
same logic
(abstracting out the size+x gubbins so Bits' new native code logic remains 
up to date).


Any thoughts?

- Adam




>> Hi Adam,
>>
>> Did you know that native memory is already tracked on the Java side for
>> direct ByteBuffers? See class java.nio.Bits. Could you make use of it?
>>
> Right, these are the fields that are exposed at runtime via 
> BufferPoolMXBean. A SA based tool could read from a core file. I can't 
> tell if this is enough for Adam, it may be that the his tool reveals 
> more details on the buffers in the pools.
> 
> -Alan


P.S. Code:

diff --git 
a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template 
b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
--- a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
+++ b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
@@ -85,7 +85,7 @@
                 // Paranoia
                 return;
             }
-            UNSAFE.freeMemory(address);
+            UNSAFE.freeDBBMemory(address);
             address = 0;
             Bits.unreserveMemory(size, capacity);
         }
@@ -118,7 +118,7 @@
 
         long base = 0;
         try {
-            base = UNSAFE.allocateMemory(size);
+            base = UNSAFE.allocateDBBMemory(size);
         } catch (OutOfMemoryError x) {
             Bits.unreserveMemory(size, cap);
             throw x;
diff --git a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java 
b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java
--- a/src/java.base/share/classes/jdk/internal/misc/Unsafe.java
+++ b/src/java.base/share/classes/jdk/internal/misc/Unsafe.java
@@ -632,6 +632,26 @@
     }
 
     /**
+     * Allocates a new block of native memory for DirectByteBuffers, of 
the
+     * given size in bytes.  The contents of the memory are 
uninitialized;
+     * they will generally be garbage.  The resulting native pointer will
+     * never be zero, and will be aligned for all value types.  Dispose 
of
+     * this memory by calling {@link #freeDBBMemory} or resize it with
+     * {@link #reallocateDBBMemory}.
+     *
+     * @throws RuntimeException if the size is negative or too large 
+     *                          for the native size_t type
+     *
+     * @throws OutOfMemoryError if the allocation is refused by the 
system
+     *
+     * @see #getByte(long)
+     * @see #putByte(long, byte)
+     */
+    public long allocateDBBMemory(long bytes) {
+        return allocateMemory(bytes);
+    }
+ 
+    /**
      * Resizes a new block of native memory, to the given size in bytes. 
The
      * contents of the new block past the size of the old block are
      * uninitialized; they will generally be garbage.  The resulting 
native
@@ -687,6 +707,27 @@
     }
 
     /**
+     * Resizes a new block of native memory for DirectByteBuffers, to the
+     * given size in bytes.  The contents of the new block past the size 
of
+     * the old block are uninitialized; they will generally be garbage. 
The
+     * resulting native pointer will be zero if and only if the requested 
size
+     * is zero.  The resulting native pointer will be aligned for all 
value
+     * types.  Dispose of this memory by calling {@link #freeDBBMemory}, 
or
+     * resize it with {@link #reallocateDBBMemory}.  The address passed 
to
+     * this method may be null, in which case an allocation will be 
performed.
+     *
+     * @throws RuntimeException if the size is negative or too large 
+     *                          for the native size_t type
+     *
+     * @throws OutOfMemoryError if the allocation is refused by the 
system
+     *
+     * @see #allocateDBBMemory
+     */
+    public long reallocateDBBMemory(long address, long bytes) {
+        return reallocateMemory(address, bytes);
+    }
+
+    /**
      * Sets all bytes in a given block of memory to a fixed value
      * (usually zero).
      *
@@ -918,6 +959,17 @@
         checkPointer(null, address);
     }
 
+    /**
+     * Disposes of a block of native memory, as obtained from {@link
+     * #allocateDBBMemory} or {@link #reallocateDBBMemory}.  The address 
passed
+     * to this method may be null, in which case no action is taken.
+     *
+     * @see #allocateDBBMemory
+     */
+    public void freeDBBMemory(long address) {
+        freeMemory(address);
+    }
+
     /// random queries
 
     /**

Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number 
741598. 
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU


More information about the core-libs-dev mailing list