Java NIO DirectByteBuffer use and research

A Conclusion

  DirectByteBuffer and ByteBuffer biggest difference lies in the buffer memory management method. ByteBuffer using heap memory , DirectByteBuffer using heap memory outside , the advantages of heap memory that is external to perform I / O operations to a relatively small number of copies of the data, and therefore get a higher performance. Everything must have However, due to the buffer memory outside the allocated heap also introduced a series of memory allocation and recovery issues, but fortunately JDK provides a range of solutions to the problem, which is the focus of this paper is to set out.

The disadvantage of two .ByteBuffer

  I / O operation may be considered a series of substantially shift the data. For example, the number of commodity brokers and shift by fewer, the cheaper price; for I / O, the fewer the number of copies of the byte array, which is the higher I / O performance . And performs the following operation is the reason for poor performance ByteBuffer I / O operations using ByteBuffer:

  1. The heap memory buffers the data to the temporary buffer

  2. The temporary data buffer perform low-level I / O operations

  3. Provisional buffer object goes out of scope, and eventually become useless data recovered

  In contrast, since the DirectByteBuffer heap memory allocation in the external memory can be performed directly lower-level I / O operation data, reducing the number of copies obtained and therefore a higher performance.

  The question is, why do not heap buffer memory to perform low-level I / O operations directly to it?

  Guess the main reason is, in the position of JVM heap memory garbage collection operation will move the object , in order to achieve clean up the memory, so if the direct implementation of the buffer in the heap may find that the buffer memory address changing circumstances , there is no way to perform I / O operations.

Three .DirectByteBuffer memory allocation and recovery

  Because DirectByteBuffer API usage is not much difference between ByteBuffer, so this article will focus on how to apply DirectByteBuffer is to perform memory operations, and how to reclaim memory operation.

3.1. Memory Application

  In constructing DirectByteBuffer has performed memory allocation operation in which we focus Bits.reserveMemory (size, cap) and Cleaner.create (this, new Deallocator (base, size, cap)).

DirectByteBuffer ( int CAP) {                    // Package Private- 

        Super (-1, 0 , CAP, CAP);
         Boolean PA = VM.isDirectMemoryPageAligned ();
         int PS = Bits.pageSize ();
         Long size = Math.max (1L, ( Long ) + CAP (PA PS:? 0 ));
         // memory allocation preprocessing 
        Bits.reserveMemory (size, CAP); 

        Long Base = 0 ;
         the try {
            // application outer heap memory and returns the first address of the buffer memory 
            = Base unsafe.allocateMemory (size); 
        } the catch(An OutOfMemoryError X) { 
            Bits.unreserveMemory (size, CAP); 
            the throw X; 
        } 
        unsafe.setMemory (Base, size, ( byte ) 0 );
         IF (! && PA (Base% PS = 0 )) {
             // Round up Page boundary to 
            address = Base + PS - (Base & (PS -. 1 )); 
        } the else { 
            address = Base; 
        } 
        // this line of code for implementing the DirectByteBuffer when recovered, the external memory heap is freed 
        cleaner = Cleaner.create ( the this , new new Deallocator (Base, size, CAP)); 
        ATT= null;
    }    

 

  Bits.reserveMemory

  

static  void reserveMemory ( Long size, int CAP) {
         // set the maximum memory 
        IF (memoryLimitSet &&! VM.isBooted ()) { 
            maxMemory = VM.maxDirectMemory (); 
            memoryLimitSet = to true ; 
        } 
       
        // optimistic attempt predetermined direct memory (DirectMemory) memory
         // ! Optimist 
        IF (tryReserveMemory (size, CAP)) {
             return ; 
        } 
       
        // if the predetermined memory fails, then the unused memory in the direct memory recovery operation performed 
        Final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess ();

        // the retry Helping the while the enqueue Pending Reference Objects
         // Which Includes Executing Pending Cleaner (S) Which Includes
         // Cleaner (S) Free Direct Buffer Memory that 
        the while (jlra.tryHandlePendingReference ()) {
             IF (tryReserveMemory (size, CAP)) {
                 return ; 
            } 
        } 
        // trigger GC operations
         // trigger the VM apos Reference Processing 
        System.gc (); 
        
        // perform multiple cycles, memory attempts the recovery operation, if after several failed attempts OutOfMemory exception is thrown
         // A Exponential Loop with the Back-retry OFF delays
         // (this gives VM some time to do it's job)
        boolean interrupted = false;
        try {
            long sleepTime = 1;
            int sleeps = 0;
            while (true) {
                if (tryReserveMemory(size, cap)) {
                    return;
                }
                if (sleeps >= MAX_SLEEPS) {
                    break;
                }
                if (!jlra.tryHandlePendingReference()) {
                    try {
                        Thread.sleep(sleepTime);
                        sleepTime <<= 1;
                        sleeps++;
                    } catch (InterruptedException e) {
                        interrupted = true;
                    }
                }
            }

            // no luck
            throw new OutOfMemoryError("Direct buffer memory");

        } finally {
            if (interrupted) {
                // don't swallow interrupts
                Thread.currentThread().interrupt();
            }
        }
    }

 

 tryReserveMemory The main function of this method is to check the current DirectMemory memory is enough to build a buffer DirectByteBuffer and set the memory currently used by way of CAS   
// try predetermined memory 
Private static Boolean tryReserveMemory ( Long size, int CAP) { // -XX: The Total Capacity Limits MaxDirectMemorySize The Rather Within last // Actual Memory Usage, Which Will Differ When Page buffers are // the aligned. Long totalCap;
     // check whether sufficient memory
the while (CAP <= maxMemory - (totalCap = totalCapacity.get ())) {
       // if sufficient memory, try CAS disposed totalCapacity 
IF (totalCapacity.compareAndSet (totalCap, totalCap + CAP)) { reservedMemory .addAndGet (size); COUNT .incrementAndGet (); return true; } } return false; }

   Why jlra.tryHandlePendingReference can perform memory reclamation does it work? The principle is shown in the following section.

3.2. Memory release

  Conclusion: There are two ways in DirectByteBuffer direct memory buffer release

  1.ReferenceHandler thread will automatically check for recovered DirectByteBuffer, if the method is performed Cleaner.clean release the corresponding direct memory

  2. to free memory by calling SharedSecrets.getJavaLangRefAccess () method, see the specific Reference class code analysis.

3.2.1 Code Analysis

  Code is the key sentence of the direct memory release.

 cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

 

  Deallocator code is as follows:

    private static class Deallocator
        implements Runnable
    {

        private static Unsafe unsafe = Unsafe.getUnsafe();
        //直接内存缓冲区的首地址
        private long address;
        private long size;
        private int capacity;

        private Deallocator(long address, long size, int capacity) {
            assert (address != 0);
            this.address = address;
            this.size =size;
             the this .capacity = Capacity; 
        } 

        public  void RUN () {
             IF (address == 0 ) {
                 // Paranoia 
                return ; 
            } 
            // release memory 
            unsafe.freeMemory (address); 
            address = 0 ;
             // scheduled memory - freed memory 
            Bits.unreserveMemory (size, Capacity); 
        } 

    }

 

   Cleaner maintains a bidirectional internal queue, these are defined as shown below.

       Please note the following key points:

  Cleaner inherited PhantomReference specter references, and maintained a ReferenceQueue <Object> queue.

  

public class Cleaner extends PhantomReference<Object> {
    private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue();
    private static Cleaner first = null;
    private Cleaner next = null;
    private Cleaner prev = null;
    private final Runnable thunk;
   
       private static synchronized Cleaner add(Cleaner var0) {
        if (first != null) {
            var0.next = first;
            first.prev = var0;
        }

        first = var0;
        return var0;
    }    
   
     private Cleaner(Object var1, Runnable var2) {
        super(var1, dummyQueue);
        this.thunk = var2;
    }
     
     public static Cleaner create(Object var0, Runnable var1) {
        return var1 == null ? null : add(new Cleaner(var0, var1));
    }
        // 执行Dealloactor.run()
        public void clean() {
        if (remove(this)) {
            try {
                this.thunk.run();
            } catch (final Throwable var2) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }

                        System.exit(1);
                        return null;
                    }
                });
            }

        }
    }
} 

 

  Reference specter defined as follows:

public class PhantomReference<T> extends Reference<T> {
   public T get() {
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

 

   The question is, said so much in the end who is calling DirectByteBuffer memory recovery codes (Cleaner.clean () -> Deallocator.run ())

 Reference code says it all:
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
Mainly performed continuously tryHandlePending method shown below the main code ReferenceHandler
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }

 

    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // The waiting on the lock may cause an OutOfMemoryError
                    // because it may try to allocate exception objects.
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            // Give other threads CPU time so they hopefully drop some live references
            // and GC reclaims some space.
            // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
            // persistently throws OOME for some time...
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }
        // Cleaner.clean 方法调用处  
        // Fast path for cleaners
        if (c != null) {
            c.clean(); 
            return true;
        }

        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

 



 

Guess you like

Origin www.cnblogs.com/smallke/p/11224650.html