• JVM之DirectByteBuffer


    DirectByteBuffer

      不同于Java Heap,这里的直接内存指堆外内存,JVM之外的一块区域,不受JVM的管控。
      DirectByteBuffer该类本身还是位于Java内存模型的堆中。堆内内存是JVM可以直接管控、操纵。而DirectByteBuffer中的unsafe.allocateMemory(size);是个一个native方法,这个方法分配的是堆外内存,通过C的malloc来进行分配的。分配的内存是系统本地的内存,并不在Java的内存中,也不属于JVM管控范围,所以在DirectByteBuffer一定会存在某种方式来操纵堆外内存。
      在DirectByteBuffer的父类Buffer中有个address属性(堆外内存的地址),只会被直接缓存给使用到。之所以将address属性升级放在Buffer中,是为了在JNI调用GetDirectBufferAddress时提升它调用的速率。

    ——————————————————————————————————————

    为什么:
    1.对垃圾回收停顿的改善。
      因为full gc 意味着彻底回收,彻底回收时,垃圾收集器会对所有分配的堆内内存进行完整的扫描,这意味着一个重要的事实——这样一次垃圾收集对Java应用造成的影响,跟堆的大小是成正比的。过大的堆会影响Java应用的性能。如果使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
    2.在某些场景下可以提升程序I/O操纵的性能。少去了将数据从堆内内存拷贝到堆外内存的步骤。
      创建DirectByteBuffer不如HeapByteBuffer快,但是本地IO(从操作系统本地获取数据,比如文件、socket网络数据)修改数据DirectByteBuffer是更快的。HeapByteBuffer的缓冲区是Java中的Java数组,使用HeapByteBuffer读取一个文件,操作系统会先把文件读取到操作系统管理的内存中。然后在把操作系统管理的这块内存的数据复制到jvm管理的内存(即HeapByteBuffer管理的Java字节数组)中,然后HeapByteBuffer对Java字节数组进行操作。而DirectByteBuffer(1)先在操作系统内核所管理的内存缓冲区中(文件系统页)开辟一块内存空间(作为缓冲区),文件数据可以直接从操作系统复制到该缓冲区中;(2)用户写入和读取数据就可以直接操作这块缓冲区,比使用HeapByteBuffer少了一个步骤,效率自然提升了。

    ——————————————————————————————————————

    怎么用:
    这里给出代码示例,使用堆内内存以及堆外内存创建bytebuffer:

    public class TestByteBuffer {
        public static void main(String[] args) {
            directAndHeapSpeedCompare();
        }
    
        private static void directAndHeapSpeedCompare() {
            int length = 10000;
            directExecuteTime(length);
            heapExecuteTime(length);
        }
    
        private static void directExecuteTime(int length) {
            long startTime = System.currentTimeMillis();
            ByteBuffer[] byteBufferArray = new ByteBuffer[length];
            for (int i = 0; i < length; i++) {
                byteBufferArray[i] = ByteBuffer.allocateDirect(1024);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("创建" + length + "个DirectByteBuffer所消耗的时间:" + (endTime - startTime));
        }
    
        private static void heapExecuteTime(int length) {
            long startTime = System.currentTimeMillis();
            ByteBuffer[] byteBufferArray = new ByteBuffer[length];
            for (int i = 0; i < length; i++) {
                byteBufferArray[i] = ByteBuffer.allocate(1024);
            }
            long endTime = System.currentTimeMillis();
            System.out.println("创建" + length + "个HeapByteBuffer所消耗的时间:" + (endTime - startTime));
        }
    
    }

    可以看到结果中,直接创建HeapByteBuffer是比DirectByteBuffer快的(一定范围内,如果HeapByteBuffer对象过多,引起GC,时间上不一定)。
      另外,注意这里当内存大小超过了限制(将代码中的length调大),都会报出OOME,但两者的说明不同:

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space...
    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory...

    ——————————————————————————————————————

    最佳实践:
      堆外内存适用于生命周期中等或较长的对象。( 如果是生命周期较短的对象,在YGC的时候就被回收了,就不存在大内存且生命周期较长的对象在FGC对应用造成的性能影响 )。
      直接的文件拷贝操作,或者I/O操作。直接使用堆外内存就能少去内存从用户内存拷贝到系统内存的操作,因为I/O操作是系统内核内存和设备间的通信,而不是通过程序直接和外设通信的。
      同时,还可以使用 池+堆外内存 的组合方式,来对生命周期较短,但涉及到I/O操作的对象进行堆外内存的再使用。( Netty中就使用了该方式)。

    ——————————————————————————————————————

    堆外内存与堆内内存池对比:
      内存池:主要用于两类对象:①生命周期较短,且结构简单的对象,在内存池中重复利用这些对象能增加CPU缓存的命中率,从而提高性能;②加载含有大量重复对象的大片数据,此时使用内存池能减少垃圾回收的时间。
      堆外内存:它和内存池一样,也能缩短垃圾回收时间,但是它适用的对象和内存池完全相反。内存池往往适用于生命期较短的可变对象,而生命期中等或较长的对象,正是堆外内存要解决的。

    ——————————————————————————————————————

    堆外内存的特点:
      对于大内存有良好的伸缩性
      对垃圾回收停顿的改善可以明显感觉到
      在进程间可以共享,减少虚拟机间的复制

    参考:
      https://cloud.tencent.com/developer/article/1152616  
      https://www.cnblogs.com/felixzh/p/12013017.html

  • 相关阅读:
    基于MQTT协议进行应用开发
    shell ss命令
    组播基本概念、IGMP、IGMP监听学习笔记
    关于Android的HAL的一些理解
    Android HAL层与Linux Kernel层驱动开发简介
    YPBPR_PC下图像有毛刺或者水纹干扰的处理办法
    CHAKRA3 UART2
    实际用户ID和有效用户ID (三) *****
    svn使用svnsync实现双机热备
    pyspider介绍及安装
  • 原文地址:https://www.cnblogs.com/bruceChan0018/p/15055082.html
Copyright © 2020-2023  润新知