【Java深层系列】「技术盲区」让我们一起探索一下Netty(Java)底层的“零拷贝Zero-Copy”技术(上)

简介: 【Java深层系列】「技术盲区」让我们一起探索一下Netty(Java)底层的“零拷贝Zero-Copy”技术(上)

Netty的零拷贝


Netty中的零拷贝与我们传统理解的零拷贝不太一样。


传统的零拷贝指的是数据传输过程中,不需要CPU进行数据的拷贝。主要是数据在用户空间与内核中间之间的拷贝。


传统意义的零拷贝

Zero-Copy describes computer operations in which the CPU does not perform the task of copying data from one memory area to another.


在发送数据的时候,传统的实现方式是:


  • File.read(bytes)
  • Socket.send(bytes)


这种方式需要四次数据拷贝和四次上下文切换:


  • 数据从磁盘读取到内核的read buffer
  • 数据从内核缓冲区拷贝到用户缓冲区
  • 数据从用户缓冲区拷贝到内核的socket buffer
  • 数据从内核的socket buffer拷贝到网卡接口的缓冲区


明显上面的第二步和第三步是没有必要的,通过java的FileChannel.transferTo方法,可以避免上面两次多余的拷贝(当然这需要底层操作系统支持)。


  1. 调用transferTo,数据从文件由DMA引擎拷贝到内核read buffer
  2. 接着DMA从内核read buffer将数据拷贝到网卡接口buffer


上面的两次操作都不需要CPU参与,所以就达到了零拷贝。




Netty中的零拷贝


Netty中也用到了FileChannel.transferTo方法,所以Netty的零拷贝也包括上面将的操作系统级别的零拷贝,除此之外,在ByteBuf的实现上,Netty也提供了零拷贝的一些实现。


关于ByteBuffer,Netty提供了两个接口:


  • ByteBuf
  • ByteBufHolder

对于ByteBuf,Netty提供了多种实现:

  • Heap ByteBuf:直接在堆内存分配
  • Direct ByteBuf:直接在内存区域分配而不是堆内存
  • CompositeByteBuf:组合Buffer



Direct Buffers(直接内存)


直接在内存区域分配空间,而不是在堆内存中分配。


  • 如果使用传统的堆内存分配,当我们需要将数据通过socket发送的时候,就需要从堆内存拷贝到直接内存,然后再由直接内存拷贝到网卡接口层。
  • Netty提供的直接Buffer,直接将数据分配到内存空间,从而避免了数据的拷贝,实现了零拷贝。



堆外内存


如果在JVM 内部执行 I/O 操作时,必须将数据拷贝到堆外内存,才能执行系统调用。 VM语言都会存在的问题,那么为什么操作系统不能直接使用JVM堆内存进行 I/O 的读写呢?


主要有两点原因:


  1. 操作系统并不感知JVM 的堆内存,而且 JVM 的内存布局与操作系统所分配的是不一样的,操作系统并不会按照 JVM 的行为来读写数据。


  1. 同一个对象的内存地址随着 JVM GC 的执行可能会随时发生变化,例如 JVM GC 的过程中会通过压缩来减少内存碎片,这就涉及对象移动的问题了。


Netty 在进行 I/O 操作时都是使用的堆外内存,可以避免数据从 JVM 堆内存到堆外内存的拷贝。


  • JDK 告诉我们,NIO操作并不适合直接在堆上操作。由于 heap 受到 GC 的直接管理,在 IO 写入的过程中 GC 可能会进行内存空间整理,这导致了一次 IO 写入的内存地址不完整。
  • JNI(Java Native Inteface)在调用 IO 操作的 C 类库时,规定了写入时地址不能失效,这就导致了不能在 heap 上直接进行 IO 操作。在 IO 操作的时候禁止 GC 也是一个选项,如果 IO 时间过长,那么则可能会引起堆空间溢出。



Composite Buffers


传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组中的数据拷贝到新的数组中。但是使用Netty提供的组合ByteBuf,就可以避免这样的操作,因为CompositeByteBuf并没有真正将多个Buffer组合起来,而是保存了它们的引用,从而避免了数据的拷贝,实现了零拷贝。


FileChannel.transferTo的使用


Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。



总结


Netty的零拷贝体现在三个方面:


  1. Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝。


  • 如果使用传统的堆内存(HEAP BUFFERS)进行Socket读写,JVM会将堆内存Buffer拷贝一份到直接内存中,然后才写入Socket中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。


  1. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象,用户可以像操作一个Buffer那样方便的对组合Buffer进行操作,避免了传统通过内存拷贝的方式将几个小Buffer合并成一个大的Buffer。


  1. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题




关于堆外内存的回收


堆外内存的回收其实依赖于我们的GC机制


  • 首先,我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和GC也有关,那就是GC能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。


  • DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference其实主要是用来跟踪对象何时被回收的,它不能影响GC决策。


  • GC过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在GC完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块。



为什么要主动调用System.gc


System.gc()会对新生代的老生代都会进行内存回收,这样会比较彻底地回收,DirectByteBuffer对象以及他们关联的堆外内存.


DirectByteBuffer对象本身其实是很小的,但是它后面可能关联了一个非常大的堆外内存,因此我们通常称之为冰山对象。


做ygc的时候会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收了,但是无法对old里的DirectByteBuffer对象及其堆外内存进行回收,这也是我们通常碰到的最大的问题.


如果有大量的DirectByteBuffer对象移到了old,但是又一直没有做cms gc或者full gc,而只进行ygc,那么我们的物理内存可能被慢慢耗光,但是我们还不知道发生了什么,因为heap明明剩余的内存还很多。




资源学习


www.jianshu.com/p/61a7916b3…




相关文章
|
20天前
|
NoSQL Java 数据库连接
深入探索 Java 后台开发的核心技术
【4月更文挑战第5天】本文探讨了Java后台开发的关键技术,包括Spring框架与Spring Boot的使用,MyBatis和Hibernate的ORM选择,关系型与NoSQL数据库的适用场景,线程池与异步处理在并发中的作用,微服务架构及RESTful API设计。这些核心技术有助于开发者打造稳定、高性能的Java后台系统,适应不断发展的云计算和人工智能需求。
|
27天前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
75 0
|
1天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
1天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
1天前
|
Java API
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
|
1天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
7天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
8天前
|
存储 数据可视化 安全
Java全套智慧校园系统源码springboot+elmentui +Quartz可视化校园管理平台系统源码 建设智慧校园的5大关键技术
智慧校园指的是以物联网为基础的智慧化的校园工作、学习和生活一体化环境,这个一体化环境以各种应用服务系统为载体,将教学、科研、管理和校园生活进行充分融合。无处不在的网络学习、融合创新的网络科研、透明高效的校务治理、丰富多彩的校园文化、方便周到的校园生活。简而言之,“要做一个安全、稳定、环保、节能的校园。
34 6
|
9天前
|
监控 前端开发 算法
Java技术体系
Java技术体系(韩顺平老师整理)
9 0
|
19天前
|
存储 安全 Java
Java中实现高效的字符串拼接技术
【4月更文挑战第6天】在Java编程中,字符串拼接是一个常见的操作。然而,由于字符串的不可变性,频繁的拼接操作可能会导致性能问题。本文将探讨Java中实现高效字符串拼接的技术,包括使用StringBuilder类、StringBuffer类以及Java 8中的StringJoiner类。通过对比这些技术的优缺点,我们将为您提供在不同场景下选择合适的字符串拼接方法的建议。