How to garbage collect a direct buffer in Java

前端 未结 6 1794
遥遥无期
遥遥无期 2020-12-05 00:02

I have a memory leak that I have isolated to incorrectly disposed direct byte buffers.

ByteBuffer buff = ByteBuffer.allocateDirect(7777777);

The GC colle

相关标签:
6条回答
  • 2020-12-05 00:42

    The allocated memory is realized through a native libary. This memory will be freed when the ByteBuffer#finalize method is called, iaw when the Buffer is gc'd. Have a look at the allocate() and finalize() implementations of DirectByteBufferImpl.

    buff.clear() is not necessary, System.gc() will only help if, like others already mentioned, there's no more reference left to the ByteBuffer object.

    0 讨论(0)
  • 2020-12-05 00:43

    The ByteBuffer documentation says:

    A direct byte buffer may be created by invoking the allocateDirect factory method of this class. The buffers returned by this method typically have somewhat higher allocation and deallocation costs than non-direct buffers. The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measureable gain in program performance.

    In particular, the statement "may reside outside of the normal garbage-collected heap" seems relevant to your example.

    0 讨论(0)
  • 2020-12-05 00:51

    Here is a refined implementation that will work for any direct buffer:

    public static void destroyBuffer(Buffer buffer) {
        if(buffer.isDirect()) {
            try {
                if(!buffer.getClass().getName().equals("java.nio.DirectByteBuffer")) {
                    Field attField = buffer.getClass().getDeclaredField("att");
                    attField.setAccessible(true);
                    buffer = (Buffer) attField.get(buffer);
                }
    
                Method cleanerMethod = buffer.getClass().getMethod("cleaner");
                cleanerMethod.setAccessible(true);
                Object cleaner = cleanerMethod.invoke(buffer);
                Method cleanMethod = cleaner.getClass().getMethod("clean");
                cleanMethod.setAccessible(true);
                cleanMethod.invoke(cleaner);
            } catch(Exception e) {
                throw new QuartetRuntimeException("Could not destroy direct buffer " + buffer, e);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-05 00:55

    The DBB will be deallocated once it hits the reference queue, and the finalizer is run. However, as we cannot depend on a finalizer to run, we can use reflection to manually call its "cleaner".

    Using reflection:

    /**
    * DirectByteBuffers are garbage collected by using a phantom reference and a
    * reference queue. Every once a while, the JVM checks the reference queue and
    * cleans the DirectByteBuffers. However, as this doesn't happen
    * immediately after discarding all references to a DirectByteBuffer, it's
    * easy to OutOfMemoryError yourself using DirectByteBuffers. This function
    * explicitly calls the Cleaner method of a DirectByteBuffer.
    * 
    * @param toBeDestroyed
    *          The DirectByteBuffer that will be "cleaned". Utilizes reflection.
    *          
    */
    public static void destroyDirectByteBuffer(ByteBuffer toBeDestroyed)
        throws IllegalArgumentException, IllegalAccessException,
        InvocationTargetException, SecurityException, NoSuchMethodException {
    
      Preconditions.checkArgument(toBeDestroyed.isDirect(),
          "toBeDestroyed isn't direct!");
    
      Method cleanerMethod = toBeDestroyed.getClass().getMethod("cleaner");
      cleanerMethod.setAccessible(true);
      Object cleaner = cleanerMethod.invoke(toBeDestroyed);
      Method cleanMethod = cleaner.getClass().getMethod("clean");
      cleanMethod.setAccessible(true);
      cleanMethod.invoke(cleaner);
    
    }
    
    0 讨论(0)
  • 2020-12-05 00:58

    As long as you are relying on sun (oracle) specific implementation, a better choice than trying to change the visibility of java.nio.DirectByteBuffer is to use the sun.nio.ch.DirectBuffer interface via reflections.

    /**
     * Sun specific mechanisms to clean up resources associated with direct byte buffers.
     */
    @SuppressWarnings("unchecked")
    private static final Class<? extends ByteBuffer> SUN_DIRECT_BUFFER = (Class<? extends ByteBuffer>) lookupClassQuietly("sun.nio.ch.DirectBuffer");
    
    private static final Method SUN_BUFFER_CLEANER;
    
    private static final Method SUN_CLEANER_CLEAN;
    
    static
    {
        Method bufferCleaner = null;
        Method cleanerClean = null;
        try
        {
            // operate under the assumption that if the sun direct buffer class exists,
            // all of the sun classes exist
            if (SUN_DIRECT_BUFFER != null)
            {
                bufferCleaner = SUN_DIRECT_BUFFER.getMethod("cleaner", (Class[]) null);
                Class<?> cleanClazz = lookupClassQuietly("sun.misc.Cleaner");
                cleanerClean = cleanClazz.getMethod("clean", (Class[]) null);
            }
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
        SUN_BUFFER_CLEANER = bufferCleaner;
        SUN_CLEANER_CLEAN = cleanerClean;
    }
    
    public static void releaseDirectByteBuffer(ByteBuffer buffer)
    {
        if (SUN_DIRECT_BUFFER != null && SUN_DIRECT_BUFFER.isAssignableFrom(buffer.getClass()))
        {
            try
            {
                Object cleaner = SUN_BUFFER_CLEANER.invoke(buffer, (Object[]) null);
                SUN_CLEANER_CLEAN.invoke(cleaner, (Object[]) null);
            }
            catch (Throwable t)
            {
                logger.trace("Exception occurred attempting to clean up Sun specific DirectByteBuffer.", t);
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-05 00:59

    I suspect that somewhere your application has a reference to the ByteBuffer instance(s) and that is preventing it from being garbage collected.

    The buffer memory for a direct ByteBuffer is allocated outside of the normal heap (so that the GC doesn't move it!!). However, the ByteBuffer API provides no method for explicitly disposing of / deallocating a buffer. So I assume that the garbage collector will do it ... once it determines that the ByteBuffer object is no longer referenced.

    0 讨论(0)
提交回复
热议问题