精华内容
下载资源
问答
  • DirectByteBuffer

    2013-11-18 14:42:05
    byte buffer一般在网络交互过程中java使用得比较多,尤其是...而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。 1、HeapByteBuffer与DirectByteBuffer,在原理上,
    byte buffer一般在网络交互过程中java使用得比较多,尤其是以NIO的框架中;
    看名字就知道是以字节码作为缓冲的,先buffer一段,然后flush到终端。
    而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。


    1、HeapByteBufferDirectByteBuffer,在原理上,前者可以看出分配的buffer是在heap区域的,其实真正flush到远程的时候会先拷贝得到直接内存,再做下一步操作(考虑细节还会到OS级别的内核区直接内存),其实发送静态文件最快速的方法是通过OS级别的send_file,只会经过OS一个内核拷贝,而不会来回拷贝;在NIO的框架下,很多框架会采用DirectByteBuffer来操作,这样分配的内存不再是在java heap上,而是在C heap上,经过性能测试,可以得到非常快速的网络交互,在大量的网络交互下,一般速度会比HeapByteBuffer要快速好几倍。
    最基本的情况下
    分配HeapByteBuffer的方法是:
    ByteBuffer.allocate(int capacity);参数大小为字节的数量

    分配DirectByteBuffer的方法是:
    ByteBuffer.allocateDirect(int capacity);//可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的,不过你可以通过反射的手段得到实例进而做操作,当然你需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,其实并不是不安全,而是交给程序员来操作,它可能会因为程序员的能力而导致不安全,而并非它本身不安全。
    由于HeapByteBufferDirectByteBuffer类都是default类型的,所以你无法字节访问到,你只能通过ByteBuffer间接访问到它,因为JVM不想让你访问到它,对了,JVM不想让你访问到它肯定就有它不可告人的秘密;后面我们来跟踪下他的秘密吧。


    2、前面说到了,这块区域不是在java heap上,那么这块内存的大小是多少呢?默认是一般是64M,可以通过参数:-XX:MaxDirectMemorySize来控制,你够牛的话,还可以用代码控制,呵呵,这里就不多说了。


    3、直接内存好,我们为啥不都用直接内存?请注意,这个直接内存的释放并不是由你控制的,而是由full gc来控制的,直接内存会自己检测情况而调用system.gc(),但是如果参数中使用了DisableExplicitGC 那么这是个坑了,所以啊,这玩意,设置不设置都是一个坑坑,所以java的优化有没有绝对的,只有针对实际情况的,针对实际情况需要对系统做一些拆分做不同的优化。


    4、那么full gc不触发,我想自己释放这部分内存有方法吗?可以的,在这里没有什么是不可以的,呵呵!私有属性我们都任意玩他,还有什么不可以玩的;我们看看它的源码中DirectByteBuffer发现有一个:Cleaner,貌似是用来搞资源回收的,经过查证,的确是,而且又看到这个对象是sun.misc开头的了,此时既惊喜又郁闷,呵呵,只要我能拿到它,我就能有希望消灭掉了;下面第五步我们来做个试验。


    5、因为我们的代码全是私有的,所以我要访问它不能直接访问,我需要通过反射来实现,OK,我知道要调用cleaner()方法来获取它Cleaner对象,进而通过该对象,执行clean方法;(付:以下代码大部分也取自网络上的一篇copy无数次的代码,但是那个代码是有问题的,有问题的部分,我将用红色标识出来,如果没有哪条代码是无法运行的

    1. import java.nio.ByteBuffer;
    2. import sun.nio.ch.DirectBuffer;
    3. public class DirectByteBufferCleaner {
    4. public static void clean(final ByteBuffer byteBuffer)
    5. {
    6.               
    7. if (byteBuffer.isDirect())
    8. {
    9.                  
    10. ((DirectBuffer)byteBuffer).cleaner().clean();
    11.               
    12. }
    13.         }
    14. }
    复制代码

    上述类你可以在任何位置建立都可以,这里多谢一楼的回复,以前我的写法是见到DirectByteBuffer类是Default类型的,因此这个类无法直接引用到,是通过反射去找到cleaner的实例,进而调用内部的clean方法,那样做麻烦了,其实并不需要那么麻烦,因为DirectByteBuffer implements了DirectBuffer,而DirectBuffer本身是public的,所以通过接口去调用内部的Clear对象来做clean方法。
    展开全文
  • http://www.importnew.com/19191.htmlbyte buffer一般在网络交互过程中java使用得比较多,尤其是...而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。1、HeapByteBuff...

    http://www.importnew.com/19191.html

    byte buffer一般在网络交互过程中java使用得比较多,尤其是以NIO的框架中;

    看名字就知道是以字节码作为缓冲的,先buffer一段,然后flush到终端。

    而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。

    1、HeapByteBufferDirectByteBuffer,在原理上,前者可以看出分配的buffer是在heap区域的,其实真正flush到远程的时候会先拷贝得到直接内存,再做下一步操作(考虑细节还会到OS级别的内核区直接内存),其实发送静态文件最快速的方法是通过OS级别的send_file,只会经过OS一个内核拷贝,而不会来回拷贝;在NIO的框架下,很多框架会采用DirectByteBuffer来操作,这样分配的内存不再是在java heap上,而是在C heap上,经过性能测试,可以得到非常快速的网络交互,在大量的网络交互下,一般速度会比HeapByteBuffer要快速好几倍。

    最基本的情况下

    分配HeapByteBuffer的方法是:

    1
    ByteBuffer.allocate(intcapacity);参数大小为字节的数量

    分配DirectByteBuffer的方法是:

    1
    ByteBuffer.allocateDirect(intcapacity);//可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的,不过你可以通过反射的手段得到实例进而做操作,当然你需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,其实并不是不安全,而是交给程序员来操作,它可能会因为程序员的能力而导致不安全,而并非它本身不安全。

    由于HeapByteBufferDirectByteBuffer类都是default类型的,所以你无法字节访问到,你只能通过ByteBuffer间接访问到它,因为JVM不想让你访问到它,对了,JVM不想让你访问到它肯定就有它不可告人的秘密;后面我们来跟踪下他的秘密吧。

    2、前面说到了,这块区域不是在java heap上,那么这块内存的大小是多少呢?默认是一般是64M,可以通过参数:-XX:MaxDirectMemorySize来控制,你够牛的话,还可以用代码控制,呵呵,这里就不多说了。

    3、直接内存好,我们为啥不都用直接内存?请注意,这个直接内存的释放并不是由你控制的,而是由full gc来控制的,直接内存会自己检测情况而调用system.gc(),但是如果参数中使用了DisableExplicitGC 那么这是个坑了,所以啊,这玩意,设置不设置都是一个坑坑,所以java的优化有没有绝对的,只有针对实际情况的,针对实际情况需要对系统做一些拆分做不同的优化。

    4、那么full gc不触发,我想自己释放这部分内存有方法吗?可以的,在这里没有什么是不可以的,呵呵!私有属性我们都任意玩他,还有什么不可以玩的;我们看看它的源码中DirectByteBuffer发现有一个:Cleaner,貌似是用来搞资源回收的,经过查证,的确是,而且又看到这个对象是sun.misc开头的了,此时既惊喜又郁闷,呵呵,只要我能拿到它,我就能有希望消灭掉了;下面第五步我们来做个试验。

    5、因为我们的代码全是私有的,所以我要访问它不能直接访问,我需要通过反射来实现,OK,我知道要调用cleaner()方法来获取它Cleaner对象,进而通过该对象,执行clean方法;(付:以下代码大部分也取自网络上的一篇copy无数次的代码,但是那个代码是有问题的,有问题的部分,我将用红色标识出来,如果没有哪条代码是无法运行的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    importjava.nio.ByteBuffer;
    importsun.nio.ch.DirectBuffer;
     
    publicclass DirectByteBufferCleaner {
     
            publicstatic void clean(finalByteBuffer byteBuffer) {
                  if(byteBuffer.isDirect()) {
                     ((DirectBuffer)byteBuffer).cleaner().clean();
                  }
            }
    }

    上述类你可以在任何位置建立都可以,这里多谢一楼的回复,以前我的写法是见到DirectByteBuffer类是Default类型的,因此这个类无法直接引用到,是通过反射去找到cleaner的实例,进而调用内部的clean方法,那样做麻烦了,其实并不需要那么麻烦,因为DirectByteBuffer implements了DirectBuffer,而DirectBuffer本身是public的,所以通过接口去调用内部的Clear对象来做clean方法。

    我们下面来做测试来证明这个程序是有效地回收的:

    在任意一个地方写一段main方法来调用,我这里就直接写在这个类里面了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    publicstatic void sleep(longi) {
        try{
              Thread.sleep(i);
         }catch(Exception e) {
              /*skip*/
         }
    }
    publicstatic void main(String []args) throwsException {
           ByteBuffer buffer = ByteBuffer.allocateDirect(1024* 1024* 100);
           System.out.println("start");
           sleep(10000);
           clean(buffer);
           System.out.println("end");
           sleep(10000);
    }

    这里分配了100M内存,为了将结果看清楚,在执行前,执行后分别看看延迟10s,当然你可以根据你的要求自己改改。请提前将OS的资源管理器打开,看看当前使用的内存是多少,如果你是linux当然是看看free或者用top等命令来看;本地程序我是用windows完成,在运行前机器的内存如下图所示:

    开始运行在输入start后,但是未输出end前,内存直接上升将近100m。

    在输入end后发现内存立即降低到2.47m,说明回收是有效的。

    此时可以观察JVM堆的内存,不会有太多的变化,注意:JVM本身启动后也有一些内存开销,所以不要将那个开销和这个绑定在一起;这里之所以一次性申请100m也是为了看清楚过程,其余的可以做实验玩玩了。


    展开全文
  • DirectByteBuffer简介

    2017-05-02 23:25:26
    ByteBuffer有两个实现一个为,HeapByteBuffer,另一个为DirectByteBuffer, 在上一篇文章中我们看了HeapByteBuffer,今天来看另外一个DirectByteBuffer。 下面是两种ByteBuffer创建的方法: 1.HeapByteBuffer ...
    Java NIO ByteBuffer详解:[url]http://donald-draper.iteye.com/blog/2357084[/url]
    MappedByteBuffer定义:[url]http://donald-draper.iteye.com/blog/2371594[/url]
    Reference定义(PhantomReference,Cleaner):[url]http://donald-draper.iteye.com/blog/2371661[/url]
    ByteBuffer有两个实现一个为,HeapByteBuffer,另一个为DirectByteBuffer,
    在上一篇文章中我们看了HeapByteBuffer,今天来看另外一个DirectByteBuffer。
    下面是两种ByteBuffer创建的方法:
    1.HeapByteBuffer
    //ByteBuffer,创建HeapByteBuffer方法
    public static ByteBuffer allocate(int capacity) {
    if (capacity < 0)
    throw new IllegalArgumentException();
    return new HeapByteBuffer(capacity, capacity);
    }

    HeapByteBuffer使用的java堆内存
    2.DirectByteBuffer
    //ByteBuffer,创建DirectByteBuffer方法
    public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
    }

    为了更好的理解两者的区别,先来读一下ByteBuffer的java doc

    /*<a name="direct">
    * <h4> Direct <i>vs.</i> non-direct buffers </h4>
    *
    * <p> A byte buffer is either <i>direct</i> or <i>non-direct</i>. Given a
    * direct byte buffer, the Java virtual machine will make a best effort to
    * perform native I/O operations directly upon it. That is, it will attempt to
    * avoid copying the buffer's content to (or from) an intermediate buffer
    * before (or after) each invocation of one of the underlying operating
    * system's native I/O operations.
    *ByteBuffer有direct(DirectByteBuffer)和non-direct(HeapByteBuffer)两种。
    Java虚拟机将会直接依赖于direct类型的ByteBuffer,尽最大努力执行本地的IO操作。
    在进行系统底层的IO操作前或后,尝试避免直接拷贝buffer。
    * <p> A direct byte buffer may be created by invoking the {@link
    * #allocateDirect(int) 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.
    *direct类型的buffer通过allocateDirect(int)方法创建。direct类型的buffer分配空间和
    重新分配空间,比non-direct类型的buffer代价某种程度上要高。由于direct类型的buffer的内容存储在正常的可回收垃圾的堆之外,所以对应用的内存使用影响不是太明显。因此强烈建议将direct类型buffer初始化分配足够大&long-lived的空间,以便底层操作系统的IO操作。如果对应用性能有一个客观的提升,则最好使用#allocateDirect(int)创建一个DirectByteBuffer。
    * <p> A direct byte buffer may also be created by {@link
    * java.nio.channels.FileChannel#map </code>mapping<code>} a region of a file
    * directly into memory. An implementation of the Java platform may optionally
    * support the creation of direct byte buffers from native code via JNI. If an
    * instance of one of these kinds of buffers refers to an inaccessible region
    * of memory then an attempt to access that region will not change the buffer's
    * content and will cause an unspecified exception to be thrown either at the
    * time of the access or at some later time.
    *一个direct类型buffer,亦可以通过java.nio.channels.FileChannel#map创建,映射文件
    直接到内存一个region。Java平台的具体实现可以选择通过JNI调用本地代码创建一个direct类型buffer。如果direct类型buffer一个具体实例引用不可访问的内存region,尝试访问region,不会改变buffer的内容,无论在访问的时间,还是访问后,将会引起一个不确定的异常抛出。
    * <p> Whether a byte buffer is direct or non-direct may be determined by
    * invoking its {@link #isDirect isDirect} method. This method is provided so
    * that explicit buffer management can be done in performance-critical code.
    *是不是direct类型的buffer,我们可以通过#isDirect方法来确定。
    */

    从上面一段话可以看出, DirectByteBuffer的字节直接存放在内存中,对提升底层IO操作的性能有利,由于DirectByteBuffer的字节直接存放在内存中,并不会影响应用的堆内存。HeapByteBuffer的字节,则是存放在 Java堆内存中。DirectByteBuffer主要用于Java NIO包中一些又进行底层IO操作的通道(SocketChannel,FileChannel等)。
    package java.nio;
    import java.io.FileDescriptor;
    import sun.misc.Cleaner;
    import sun.misc.Unsafe;
    import sun.misc.VM;
    import sun.nio.ch.DirectBuffer;
    class DirectByteBuffer
    extends MappedByteBuffer
    implements DirectBuffer
    {
    // Cached unsafe-access object,缓存安全访问对象
    protected static final Unsafe unsafe = Bits.unsafe();

    // Cached array base offset,缓存数据开始位置
    private static final long arrayBaseOffset = (long)unsafe.arrayBaseOffset(byte[].class);

    // Cached unaligned-access capability 这个的含义不是太清楚,有知道的可以留言给我,谢谢
    protected static final boolean unaligned = Bits.unaligned();

    // Base address, used in all indexing calculations
    // NOTE: moved up to Buffer.java for speed in JNI GetDirectBufferAddress
    // protected long address;

    // An object attached to this buffer. If this buffer is a view of another
    // buffer then we use this field to keep a reference to that buffer to
    // ensure that its memory isn't freed before we are done with it.
    //att为缓冲的附加物。如果缓存是两一个缓存的视图,我们可以用att标记参考的buffer,
    //以确保参与buffer的内存在我们用之前不会释放。
    private final Object att;
    //清除引用对象cleaner
    private final Cleaner cleaner;
    }

    我们来看一下这个unaligned的含义:
     protected static final boolean unaligned = Bits.unaligned();

    //Bits
    private static boolean unaligned;
    private static boolean unalignedKnown = false;
    static boolean unaligned() {
    if (unalignedKnown)
    return unaligned;
    String arch = AccessController.doPrivileged(
    new sun.security.action.GetPropertyAction("os.arch"));
    unaligned = arch.equals("i386") || arch.equals("x86")
    || arch.equals("amd64") || arch.equals("x86_64");
    unalignedKnown = true;
    return unaligned;
    }

    从Bits的unaligned我们大致可以猜出unaligned的含义,为操作系统架构是否为已知;以便我们在已知的架构下,操作物理内存。
    我们来看一下DirectByteBuffer的构造
    // Primary constructor
    DirectByteBuffer(int cap) { // package-private

    super(-1, 0, cap, cap);
    //获取系统内存使用是否分页
    boolean pa = VM.isDirectMemoryPageAligned();
    //获取分页size
    int ps = Bits.pageSize();
    //确定分页size
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    //预留内存
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
    //分配内存
    base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
    //释放预留内存
    Bits.unreserveMemory(size, cap);
    throw x;
    }
    //设置安全访问对象unsafe的起始位置
    unsafe.setMemory(base, size, (byte) 0);
    //设置缓存的起始位置
    if (pa && (base % ps != 0)) {
    // Round up to page boundary
    address = base + ps - (base & (ps - 1));
    } else {
    address = base;
    }
    //创建buffer的Cleaner
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
    }

    DirectByteBuffer的构造有几点我们要关注:
    1.
     //获取系统内存使用是否分页
    boolean pa = VM.isDirectMemoryPageAligned();

    2.
     //预留内存
    Bits.reserveMemory(size, cap);

    3.
    //分配内存
    base = unsafe.allocateMemory(size);

    4.
     //释放预留内存
    Bits.unreserveMemory(size, cap);

    5.
     //设置安全访问对象unsafe的起始位置
    unsafe.setMemory(base, size, (byte) 0);


    6.
    //设置缓存的起始位置
    if (pa && (base % ps != 0)) {
    // Round up to page boundary
    address = base + ps - (base & (ps - 1));
    } else {
    address = base;
    }

    7.
     //创建buffer的Cleaner
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap))
    ;
    下面分别来看这几点:
    1.
     //获取系统内存使用是否分页
    boolean pa = VM.isDirectMemoryPageAligned();

    //VM
    private static boolean pageAlignDirectMemory;//内存是否分页
    public static boolean isDirectMemoryPageAligned()
    {
    return pageAlignDirectMemory;
    }
    public static void saveAndRemoveProperties(Properties properties)
    {
    if(booted)
    throw new IllegalStateException("System initialization has completed");
    savedProps.putAll(properties);
    String s = (String)properties.remove("sun.nio.MaxDirectMemorySize");
    if(s != null)
    if(s.equals("-1"))
    {
    directMemory = Runtime.getRuntime().maxMemory();
    } else
    {
    long l = Long.parseLong(s);
    if(l > -1L)
    directMemory = l;
    }
    //如果系统属性中有sun.nio.PageAlignDirectMemory配置项,则pageAlignDirectMemory为true
    s = (String)properties.remove("sun.nio.PageAlignDirectMemory");
    if("true".equals(s))
    pageAlignDirectMemory = true;
    s = properties.getProperty("sun.lang.ClassLoader.allowArraySyntax");
    allowArraySyntax = s != null ? Boolean.parseBoolean(s) : defaultAllowArraySyntax;
    properties.remove("java.lang.Integer.IntegerCache.high");
    properties.remove("sun.zip.disableMemoryMapping");
    properties.remove("sun.java.launcher.diag");
    }

    2.
    //预留内存
    Bits.reserveMemory(size, cap);

    //Bits
     private static volatile long maxMemory = VM.maxDirectMemory();//最大可用内存
    private static volatile long reservedMemory;//预留内存
    private static volatile long totalCapacity;//内存使用量
    private static volatile long count;
    private static boolean memoryLimitSet = false;

    // These methods should be called whenever direct memory is allocated or
    // freed. They allow the user to control the amount of direct memory
    // which a process may access. All sizes are specified in bytes.
    static void reserveMemory(long size, int cap) {
    synchronized (Bits.class) {
    if (!memoryLimitSet && VM.isBooted()) {
    maxMemory = VM.maxDirectMemory();
    memoryLimitSet = true;
    }
    // -XX:MaxDirectMemorySize limits the total capacity rather than the
    // actual memory usage, which will differ when buffers are page
    // aligned.
    //如果有足够的内存可以使用,则更新预留内存和内存使用量
    if (cap <= maxMemory - totalCapacity) {
    reservedMemory += size;
    totalCapacity += cap;
    count++;
    return;
    }
    }
    //来及回收
    System.gc();
    try {
    Thread.sleep(100);
    } catch (InterruptedException x) {
    // Restore interrupt status
    Thread.currentThread().interrupt();
    }
    synchronized (Bits.class) {
    //如果内存使用量+需要分配的内存容量超时最大内存使用量,则抛出OutOfMemoryError
    if (totalCapacity + cap > maxMemory)
    throw new OutOfMemoryError("Direct buffer memory");
    reservedMemory += size;
    totalCapacity += cap;
    count++;
    }

    }

    3.
    //分配内存
    base = unsafe.allocateMemory(size);

    //Unsafe
    public native long allocateMemory(long l);

    4.
    //释放预留内存
    Bits.unreserveMemory(size, cap);

    //Bits
    //如果预留内存大于0,则释放预留内存,更新内存使用量
    static synchronized void unreserveMemory(long size, int cap) {
    if (reservedMemory > 0) {
    reservedMemory -= size;
    totalCapacity -= cap;
    count--;
    assert (reservedMemory > -1);
    }
    }

    5.
      //设置安全访问对象unsafe的起始位置
    unsafe.setMemory(base, size, (byte) 0);

    //Unsafe
      public void setMemory(long l, long l1, byte byte0)
    {
    setMemory(null, l, l1, byte0);
    }
    public native void setMemory(Object obj, long l, long l1, byte byte0);


    6.
     //设置缓存的起始位置
    if (pa && (base % ps != 0)) {
    // Round up to page boundary
    address = base + ps - (base & (ps - 1));
    } else {
    address = base;
    }

    //Buffer
    public abstract class Buffer {

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    //内存地址
    long address;
    }

    7.
     //创建buffer的Cleaner(清除引用对象cleaner)
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

    再看这步之前,我们来看一下Deallocator
     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;
    this.capacity = capacity;
    }
    public void run() {
    if (address == 0) {
    // Paranoia
    return;
    }
    //释放内存
    unsafe.freeMemory(address);
    address = 0;
    //释放预留内存
    Bits.unreserveMemory(size, capacity);
    }
    }

    在Reference定义(PhantomReference,Cleaner)这篇文章中,我们说过Cleaner清除引用对象的实际上是首先从引用对应队列ReferenceQueue移除引用对象,再执行清除线程thunk,完成实际的清除工作。
    //Cleaner
    private static final ReferenceQueue dummyQueue = new ReferenceQueue();
    private static Cleaner first = null;
    private Cleaner next;
    private Cleaner prev;
    private final Runnable thunk;
    public void clean()
    {
    if(!remove(this))
    return;
    try
    {
    thunk.run();
    }
    ...
    }

    在DirectByteBuffer中Cleaner的thunk为Deallocator
    从以上7步可以看,在DirectByteBuffer的构造主要是获取系统内存使用是否分页,预留内存,分配内存,设置安全访问对象unsafe的起始位置,设置缓存Buffer的起始位置address,创建buffer的Cleaner。
    再看看其他的构造
    // Invoked to construct a direct ByteBuffer referring to the block of
    // memory. A given arbitrary object may also be attached to the buffer.
    DirectByteBuffer(long addr, int cap, Object ob) {
    //ByteBuffer构造
    super(-1, 0, cap, cap);
    address = addr;
    cleaner = null;
    att = ob;
    }
    // Invoked only by JNI: NewDirectByteBuffer(void*, long)
    private DirectByteBuffer(long addr, int cap) {
    //ByteBuffer构造
    super(-1, 0, cap, cap);
    address = addr;
    cleaner = null;
    att = null;
    }
    上面这两的构造方法大同小异,
    // For memory-mapped buffers -- invoked by FileChannelImpl via reflection
    //此方法用于内存映射缓存,主要是FileChannelImpl通过反射调用
    protected DirectByteBuffer(int cap, long addr,
    FileDescriptor fd,
    Runnable unmapper)
    {
    //MappedByteBuffer构造
    super(-1, 0, cap, cap, fd);
    address = addr;
    cleaner = Cleaner.create(this, unmapper);
    att = null;
    }
    // For duplicates and slices,复制构造
    DirectByteBuffer(DirectBuffer db, // package-private
    int mark, int pos, int lim, int cap,
    int off)
    {
    super(mark, pos, lim, cap);
    //定位起始地址
    address = db.address() + off;
    cleaner = null;
    att = db;
    }

    来看其他方法
     //将DirectByteBuffer分割空间出来
    public ByteBuffer slice() {
    int pos = this.position();
    int lim = this.limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);
    int off = (pos << 0);
    assert (off >= 0);
    return new DirectByteBuffer(this, -1, 0, rem, rem, off);
    }
    //复制DirectByteBuffer
    public ByteBuffer duplicate() {
    return new DirectByteBuffer(this,
    this.markValue(),
    this.position(),
    this.limit(),
    this.capacity(),
    0);
    }
    //返回一个只读的DirectByteBufferR
    public ByteBuffer asReadOnlyBuffer() {
    return new DirectByteBufferR(this,
    this.markValue(),
    this.position(),
    this.limit(),
    this.capacity(),
    0);

    }

    我们来看DirectByteBufferR的定义
    class DirectByteBufferR
    extends DirectByteBuffer
    implements DirectBuffer
    {
    DirectByteBufferR(int cap) { // package-private
    super(cap);
    }
    // For memory-mapped buffers -- invoked by FileChannelImpl via reflection
    //
    protected DirectByteBufferR(int cap, long addr,
    FileDescriptor fd,
    Runnable unmapper)
    {
    super(cap, addr, fd, unmapper);

    }
    // For duplicates and slices
    DirectByteBufferR(DirectBuffer db, // package-private
    int mark, int pos, int lim, int cap,
    int off)
    {
    super(db, mark, pos, lim, cap, off);

    }
    //切割DirectByteBufferR剩余空间
    public ByteBuffer slice() {
    int pos = this.position();
    int lim = this.limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);
    int off = (pos << 0);
    assert (off >= 0);
    return new DirectByteBufferR(this, -1, 0, rem, rem, off);
    }
    //复制DirectByteBufferR
    public ByteBuffer duplicate() {
    return new DirectByteBufferR(this,
    this.markValue(),
    this.position(),
    this.limit(),
    this.capacity(),
    0);
    }
    //获取只读的DirectByteBufferR
    public ByteBuffer asReadOnlyBuffer() {
    return duplicate();
    }
    public ByteBuffer put(byte x) {
    throw new ReadOnlyBufferException();

    }
    public ByteBuffer put(ByteBuffer src) {
    throw new ReadOnlyBufferException();
    }
    public ByteBuffer put*(...) {
    throw new ReadOnlyBufferException();
    }
    //为Direct类型缓存区
    public boolean isDirect() {
    return true;
    }
    //只读
    public boolean isReadOnly() {
    return true;
    }
    /*相关的读操作与DirectByteBuffer基本相同*/
    }

    DirectByteBufferR为一个支持读操中的DirectByteBuffer,所有的写操作,将抛出ReadOnlyBufferException异常。
    回到DirectByteBuffer的相关操作
    //获取缓冲buffer起始地址
    public long address() {
    return address;
    }
    //获取索引i对应的内存实际地址
    private long ix(int i) {
    return address + (i << 0);
    }

    先来看put操作
    //将byte放在position位置上
    public ByteBuffer put(byte x) {
    unsafe.putByte(ix(nextPutIndex()), ((x)));
    return this;
    }

    //Buffer
    //返回当前position位置,position位置自增
    final int nextPutIndex() { // package-private
    if (position >= limit)
    throw new BufferOverflowException();
    return position++;
    }



    //将byte放在i位置上
    public ByteBuffer put(int i, byte x) {
    unsafe.putByte(ix(checkIndex(i)), ((x)));
    return this;
    }

    //Buffer
    //返回当前i位置是否有效
    final int checkIndex(int i) { // package-private
    if ((i < 0) || (i >= limit))
    throw new IndexOutOfBoundsException();
    return i;
    }

    //读取Bufffer的数据,写到当前缓冲区中
    public ByteBuffer put(ByteBuffer src) {
    //如果ByteBuffer为DirectByteBuffer
    if (src instanceof DirectByteBuffer) {
    if (src == this)
    throw new IllegalArgumentException();
    DirectByteBuffer sb = (DirectByteBuffer)src;
    //在读buffer之前,要调用flip函数,所以position与limit数据,即为缓冲区真实数据
    //这也是为什么在缓存区读写模式切换时,要调用flip函数的原因
    //获取源DirectByteBuffer的position,limit
    int spos = sb.position();
    int slim = sb.limit();
    assert (spos <= slim);
    //获取源DirectByteBuffer实际数据长度(buffer.flip())
    int srem = (spos <= slim ? slim - spos : 0);
    //获取当前DirectByteBuffer的position,limit
    int pos = position();
    int lim = limit();
    assert (pos <= lim);
    //获取当前DirectByteBuffer剩余容量
    int rem = (pos <= lim ? lim - pos : 0);
    //如果当前buffer的剩余空间小于源buffer的数据长度,则抛出BufferOverflowException
    if (srem > rem)
    throw new BufferOverflowException();
    //copy源buffer到当前buffer
    unsafe.copyMemory(sb.ix(spos), ix(pos), srem << 0);
    //重新定位源buffer和目的buffer的postion位置
    sb.position(spos + srem);
    position(pos + srem);
    } else if (src.hb != null) {
    //如果源buffer类型非DirectByteBuffer
    int spos = src.position();
    int slim = src.limit();
    assert (spos <= slim);
    int srem = (spos <= slim ? slim - spos : 0);
    //读取源buffer的字节数组中数据,写到当前缓冲区
    put(src.hb, src.offset + spos, srem);
    src.position(spos + srem);

    } else {
    //ByteBuffer
    super.put(src);
    }
    return this;
    }

    put(ByteBuffer src) 有几点要看
    1.
     //copy源buffer到当前buffer
    unsafe.copyMemory(sb.ix(spos), ix(pos), srem << 0);

    2.
     //读取源buffer的字节数组中数据,写到当前缓冲区
    put(src.hb, src.offset + spos, srem);

    我们分别来看这两点
    1.
     //copy源buffer到当前buffer
    unsafe.copyMemory(sb.ix(spos), ix(pos), srem << 0);

    //Unsafe
     public void copyMemory(long l, long l1, long l2)
    {
    copyMemory(null, l, null, l1, l2);
    }
    public native void copyMemory(Object obj, long l, Object obj1, long l1, long l2);

    2.
     //读取源buffer的字节数组中数据,写到当前缓冲区
    put(src.hb, src.offset + spos, srem);

    //读取源buffer的字节数组中数据,写到当前缓冲区
    public ByteBuffer put(byte[] src, int offset, int length) {
    if ((length << 0) > Bits.JNI_COPY_FROM_ARRAY_THRESHOLD) {
    checkBounds(offset, length, src.length);
    int pos = position();
    int lim = limit();
    assert (pos <= lim);
    //获取当前缓冲区剩余空间
    int rem = (pos <= lim ? lim - pos : 0);
    if (length > rem)
    throw new BufferOverflowException();
    Bits.copyFromArray(src, arrayBaseOffset, offset << 0,
    ix(pos), length << 0);
    //重新定位position
    position(pos + length);
    } else {
    //ByteBuffer
    super.put(src, offset, length);
    }
    return this;
    }

    //Bit
    // These numbers represent the point at which we have empirically
    // determined that the average cost of a JNI call exceeds the expense
    // of an element by element copy. These numbers may change over time.
    //JNI字节数组拷贝临界条件
    static final int JNI_COPY_TO_ARRAY_THRESHOLD = 6;
    static final int JNI_COPY_FROM_ARRAY_THRESHOLD = 6;

    // This number limits the number of bytes to copy per call to Unsafe's
    // copyMemory method. A limit is imposed to allow for safepoint polling
    // during a large copy
    //UNSAFE内存拷贝临界条件
    static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
    /**
    * Copy from given source array to destination address.
    *拷贝给定的字节数组到目的地址
    * @param src
    * source array
    * @param srcBaseOffset
    * offset of first element of storage in source array
    * @param srcPos
    * offset within source array of the first element to read
    * @param dstAddr
    * destination address
    * @param length
    * number of bytes to copy
    */
    static void copyFromArray(Object src, long srcBaseOffset, long srcPos,
    long dstAddr, long length)
    {
    long offset = srcBaseOffset + srcPos;
    while (length > 0) {
    long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
    unsafe.copyMemory(src, offset, null, dstAddr, size);
    length -= size;
    offset += size;
    dstAddr += size;
    }
    }

    从上面来看put(ByteBuffer)操作,如果ByteBuffer为DirectByteBuffer,则利用Unsafe
    将源缓冲区position和limit之前的数据拷贝到当前缓冲区。否则拷贝缓冲区字节序列数据到
    当前缓冲区。
    //写一个int值到缓冲区
    public ByteBuffer putInt(int x) {
    //position向后移动4个字节,获取当前position的内存地址,委托给putInt(long a, int x)
    putInt(ix(nextPutIndex((1 << 2))), x);
    return this;
    }
    //将x放到内存地址a上
    private ByteBuffer putInt(long a, int x) {
    //如果系统架构为已知架构,则通过Unsafe将x放到内存地址a上
    if (unaligned) {
    int y = (x);
    unsafe.putInt(a, (nativeByteOrder ? y : Bits.swap(y)));
    } else {
    Bits.putInt(a, x, bigEndian);
    }
    return this;
    }

    //Unsafe
     public native void putInt(long l, int i);

    来看get(byte )操作
    //获取当前索引位置的byte
    public byte get() {
    return ((unsafe.getByte(ix(nextGetIndex()))));
    }
    //获取索引i位置的byte
    public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
    }
    //读取当前缓冲数据,写到字节数组中
    public ByteBuffer get(byte[] dst, int offset, int length) {
    if ((length << 0) > Bits.JNI_COPY_TO_ARRAY_THRESHOLD) {
    checkBounds(offset, length, dst.length);
    //在读buffer之前,要调用flip函数,所以position与limit数据,即为缓冲区真实数据
    //这也是为什么在缓存区读写模式切换时,要调用flip函数的原因
    int pos = position();
    int lim = limit();
    assert (pos <= lim);
    int rem = (pos <= lim ? lim - pos : 0);
    if (length > rem)
    //如果字节序列长度,大于当前缓冲剩余空间,抛出BufferUnderflowException
    throw new BufferUnderflowException();
    //拷贝当前缓冲数据目的字节数组中
    Bits.copyToArray(ix(pos), dst, arrayBaseOffset,
    offset << 0,
    length << 0);
    position(pos + length);
    } else {
    super.get(dst, offset, length);
    }
    return this;
    }

    //Bit
    /**
    * Copy from source address into given destination array.
    *从源地址拷贝length字节到给定的字节数组中
    * @param srcAddr
    * source address
    * @param dst
    * destination array
    * @param dstBaseOffset
    * offset of first element of storage in destination array
    * @param dstPos
    * offset within destination array of the first element to write
    * @param length
    * number of bytes to copy
    */
    static void copyToArray(long srcAddr, Object dst, long dstBaseOffset, long dstPos,
    long length)
    {
    long offset = dstBaseOffset + dstPos;
    while (length > 0) {
    long size = (length > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : length;
    unsafe.copyMemory(null, srcAddr, dst, offset, size);
    length -= size;
    srcAddr += size;
    offset += size;
    }
    }

    从上可以看出get(byte[] dst, int offset, int length)方法,实际上为利用Bit,从当前缓冲地址拷贝length字节到字节数组dst中。
    //从缓冲区获取一个int值
    public int getInt() {
    //position向后移动4个字节,获取当前position的内存地址,委托给getInt(long a, int x)
    return getInt(ix(nextGetIndex((1 << 2))));
    }
    private int getInt(long a) {
    //如果系统架构为已知架构,则通过Unsafe从内存地址a上获取一个int值
    if (unaligned) {
    int x = unsafe.getInt(a);
    return (nativeByteOrder ? x : Bits.swap(x));
    }
    return Bits.getInt(a, bigEndian);
    }

    //Unsafe
    public native int getInt(long l);

    当然DirectByteBuffer还要其他get和put方法,这里我们就不一一介绍了,大致思路相同。
    再看其他一些方法。
    //是否为Direct类型缓冲区
    public boolean isDirect() {
    return true;
    }
    //是否只读
    public boolean isReadOnly() {
    return false;
    }

    [size=medium][b]总结:[/b][/size]
    [color=blue]在DirectByteBuffer的构造主要是获取系统内存使用是否分页,预留内存,分配内存,设置安全访问对象unsafe的起始位置,设置缓存Buffer的起始位置address,创建buffer的Cleaner。put(ByteBuffer)操作,如果ByteBuffer为DirectByteBuffer,则利用Unsafe的copyMemory方法将源缓冲区position和limit之前的数据拷贝到当前缓冲区。否则拷贝缓冲区字节序列数据到当前缓冲区。get(byte[] dst, int offset, int length)方法,实际上为利用Bit,从当前缓冲地址拷贝length字节到字节数组dst中,实际上是通过Unsafe的copyMemory方法。[/color]

    附:
    //DirectByteBuffer

    [img]http://dl2.iteye.com/upload/attachment/0124/6489/ad088067-07e3-3ef1-9c2c-7001cdb11738.png[/img]

    [img]http://dl2.iteye.com/upload/attachment/0124/6491/ecaffeb0-0956-3c28-be8d-166ee0a470c4.png[/img]

    //DirectBuffer
    package sun.nio.ch;
    import sun.misc.Cleaner;
    public interface DirectBuffer
    {
    public abstract long address();
    public abstract Object attachment();
    public abstract Cleaner cleaner();
    }
    展开全文
  • 由于HeapByteBuffer和DirectByteBuffer类都是default类型的,所以你无法字节访问到,你只能通过ByteBuffer间接访问到它,因为JVM不想让你访问到它。 分配HeapByteBuffer的方法是: ByteBuffer.allocate(int ...

    由于HeapByteBuffer和DirectByteBuffer类都是default类型的,所以你无法字节访问到,你只能通过ByteBuffer间接访问到它,因为JVM不想让你访问到它。

    分配HeapByteBuffer的方法是:

    ByteBuffer.allocate(int capacity);参数大小为字节的数量

    分配DirectByteBuffer的方法是:

    ByteBuffer.allocateDirect(int capacity);//可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的,不过你可以通过反射的手段得到实例进而做操作,当然你需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,其实并不是不安全,而是交给程序员来操作,它可能会因为程序员的能力而导致不安全,而并非它本身不安全。

     

    1、HeapByteBuffer与DirectByteBuffer,在原理上,前者可以看出分配的buffer是在heap区域的,其实真正flush到远程的时候会先拷贝得到直接内存,再做下一步操作(考虑细节还会到OS级别的内核区直接内存),其实发送静态文件最快速的方法是通过OS级别的send_file,只会经过OS一个内核拷贝,而不会来回拷贝;在NIO的框架下,很多框架会采用DirectByteBuffer来操作,这样分配的内存不再是在java heap上,而是在C heap上,经过性能测试,可以得到非常快速的网络交互,在大量的网络交互下,一般速度会比HeapByteBuffer要快速好几倍。

    2、DirectByteBuffer这块区域不是在java heap上,那么这块内存的大小是多少呢?默认是一般是64M,可以通过参数:-XX:MaxDirectMemorySize来控制。

    3、直接内存好,我们为啥不都用直接内存?请注意,这个直接内存的释放并不是由你控制的,而是由full gc来控制的,直接内存会自己检测情况而调用system.gc(),但是如果参数中使用了DisableExplicitGC(这个参数作用是禁止代码中显示调用GC),那么如何才能释放直接内存呢?

    4、那么full gc不触发,我想自己释放这部分内存有方法吗?可以的,我们看到它的源码中DirectByteBuffer发现有一个:Cleaner,是用来搞资源回收的。DirectByteBuffer类是Default类型的,因此这个类无法直接引用到,是通过反射去找到cleaner的实例,进而调用内部的clean方法,那样做麻烦了,其实并不需要那么麻烦,因为DirectByteBuffer implements了DirectBuffer,而DirectBuffer本身是public的,所以通过接口去调用内部的Clear对象来做clean方法。

    下面来做测试来证明这个程序是有效地回收的:

    import java.nio.ByteBuffer;
    import sun.nio.ch.DirectBuffer;
     
    public class DirectByteBufferCleaner {
     
            public static void clean(final ByteBuffer byteBuffer) {
                  if (byteBuffer.isDirect()) {
                     ((DirectBuffer)byteBuffer).cleaner().clean();
                  }
            }
    }
    
    public static void sleep(long i) {
        try {
              Thread.sleep(i);
         }catch(Exception e) {
              /*skip*/
         }
    }
    public static void main(String []args) throws Exception {
           ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);
           System.out.println("start");
           sleep(10000);
           clean(buffer);
           System.out.println("end");
           sleep(10000);
    }

    这里分配了100M内存,为了将结果看清楚,在执行前,执行后分别看看延迟10s,请提前将OS的资源管理器打开,看看当前使用的内存是多少,如果你是linux当然是看看free或者用top等命令来看;本地程序我是用windows完成,在运行前机器的内存如下图所示:

    开始运行在输入start后,但是未输出end前,内存直接上升将近100m。

     

    在输入end后发现内存立即降低到2.47m,说明回收是有效的。

    此时可以观察JVM堆的内存,不会有太多的变化,注意:JVM本身启动后也有一些内存开销,所以不要将那个开销和这个绑定在一起;这里之所以一次性申请100m也是为了看清楚过程。

     

    转载于:https://www.cnblogs.com/wade-luffy/p/5891512.html

    展开全文
  • byte buffer一般在网络交互过程中java使用得比较多,...而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。   1、HeapByteBuffer与DirectByteBuffer,在原理上,前...
  • 本文是笔者在研究DirectByteBuffer垃圾回收过程中引发的学习与探索。众所周知,DirectByteBuffer是一个管理直接内存的引用对象,直接内存不能通过JVM进行垃圾回收,只能通过DirectByteBuffer被回收时,调用相应的JNI...
  • HeapByteBuffer,顾名思义,是写在jvm堆上面的一个...DirectByteBuffer,底层的数据其实是维护在操作系统的内存中,而不是jvm里,DirectByteBuffer里维护了一个引用address指向了数据,从而操作数据 HeapByteBuff...
  • DirectByteBuffer内存释放

    2020-11-03 01:28:16
    我们已经知道,在网络编程中,为了避免频繁的在用户空间与内核空间拷贝数据,通常会直接从内核空间中申请内存,存放数据,在Java...return new DirectByteBuffer(capacity); } 在上述代码片段中,返回的是一个DirectBy
  • Nio-DirectByteBuffer作用

    2020-08-19 15:34:00
    由于Zookeeper中使用了DirectByteBuffer进行IO操作,在此简单介绍下DirectByteBuffer和HeapByteBuffer的区别.HeapByteBuffer是在堆上分配的内存,而DirectByteBuffer是在堆外分配的内存,又称直接内存.使用...
  • JAVA基础:DirectByteBuffer

    千次阅读 2018-12-09 13:59:47
    生活 什么是DirectByteBuffer? 什么时候用到它? DirectByteBuffer 申请与回收 DirectByteBuffer 可能会出现什么问题?
  • Java NIO之DirectByteBuffer

    2018-07-10 18:04:47
    DirectByteBuffer是Java用于实现堆外内存的一个重要类,我们可以通过该类实现堆外内存的创建、使用和销毁。
  • 上周重构Excel导入为事件模式导入,结果导入2003项目频繁遇到 OutOfMemoryError DirectByteBuffer。 69FB6EE7D2595633D98F8E6E3C6B592E.jpg 而另一个项目使用事件模式就没有问题,发现两个项目的唯一区别就是...
  • 在文章JDK源码阅读-ByteBuffer中,...但是他是一个抽象类,真正的实现分为两类:HeapByteBuffer与DirectByteBuffer。HeapByteBuffer是堆内ByteBuffer,使用byte[]存储数据,是对数组的封装,比较简单。DirectByteBu...
  • byte buffer一般在网络交互过程中java使用得比较多,...而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。   1、HeapByteBuffer与DirectByteBuffer,在原理上...
  • 关于DirectByteBuffer的问题 1、 DirectByteBuffer的内存是在用户空间还是内核空间? directBytebuffer依然是属于用户空间,在jvm所在的进程当中,只是不在gc的管理范围之内罢了,所以本质上依然是用户态的内存而已...
  • DirectByteBuffer内存回收笔记

    千次阅读 2018-08-06 20:31:06
    今天在看netty源码时候又再次遇到了DirectByteBuffer,关于DirectByteBuffer的内存回收机制,在netty框架中被封装的面目全非,但其回收机制也是万变不离其宗,下面这几篇简单易懂的文章就介绍了DirectByteBuffer的...
  • DirectByteBuffer 创建的时候,会调用cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); 见下图倒数第二行 DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); ...
  • DirectByteBuffer堆外内存

    2020-04-26 00:35:22
    DirectByteBuffer是Java用于实现堆外内存的一个重要类,我们可以通过该类实现堆外内存的创建、使用和销毁。 而DirectByteBuffer中的unsafe.allocateMemory(size);是个一个native方法,这个方法分配的是堆外内存...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 679
精华内容 271
关键字:

directbytebuffer