Netty内存管理

内存使用的目标

对于 Java 程序来说,通过合理的内存使用,减少 Full GC 的 STW 时间对于程序来说可以获得更好的性能。本文结合 Netty 来看如何对 Java 内存更合理的使用。

  • 前提:尽可能的占用内存更少
  • 预期:获得更快的程序执行速度
    于 Java 而言:减少 Full GC 的 STW 时间。

内存使用技巧

减少对象本身的大小

  • 使用基本类型而不是包装类型, 包装类型相比较基本类型而言多了 object header ,会占用更多的内存。
  • 使用 static 类变量而不是实例变量, 一般如果类是非单例的,会有多个实例,使用类变量会节省更多的内存。

Netty 用于统计等待写的请求的字节数
io.netty.channel.ChannelOutboundBuffer

private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =
          AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");

@SuppressWarnings("UnusedDeclaration")
private volatile long totalPendingSize;

Netty 使用 static AtomicLongFieldUpdater 与 volatile long 结合的形式,减少本对象的内存占用。其中 AtomicLongFieldUpdater 采用反射的形式原子的更新本类中 volatile long 类型的变量。

对内存分配预估

HashMap 在超过容量的 0.75 时会扩容为 2 倍,对于可以预知容量的 HashMap 指定 size 避免库容浪费空间。

Netty 根据接收到的数据动态调整下一个要分配 Buffer 的大小
io.netty.channel.AdaptiveRecvByteBufAllocator#record(int actualReadBytes)

private void record(int actualReadBytes) {
          // 尝试是否可以减小分配的空间来满足需求:当前实际读取的 size 是否小于或等于打算缩小的 size
          if (actualReadBytes <= SIZE_TABLE[max(0, index - INDEX_DECREMENT)]) {
              // 连续两次减小都可以
              if (decreaseNow) {
                  // 减小
                  index = max(index - INDEX_DECREMENT, minIndex);
                  nextReceiveBufferSize = SIZE_TABLE[index];
                  decreaseNow = false;
              } else {
                  decreaseNow = true;
              }
              // 判断是否实际读取的数量大于等于预估的,如果是则尝试扩容
          } else if (actualReadBytes >= nextReceiveBufferSize) {
              index = min(index + INDEX_INCREMENT, maxIndex);
              nextReceiveBufferSize = SIZE_TABLE[index];
              decreaseNow = false;
          }
      }

零拷贝 - ( Zero-copy )

  • 使用逻辑组合,代替复制
io.netty.buffer.CompositeByteBuf#addComponent
  • 使用包装,代替实际复制
byte[] bytes = data.getBytes();
ByteBuf bytebuf = Unpooled.wrappedBuffer(bytes);

使用 JDK 的 Zero-Copy 接口
io.netty.channel.DefaultFileRegion#transferTo

@Override
  public long transferTo(WritableByteChannel target, long position) throws IOException {
      long count = this.count - position;
      if (count < 0 || position < 0) {
          throw new IllegalArgumentException(
                  "position out of range: " + position +
                  " (expected: 0 - " + (this.count - 1) + ')');
      }
      if (count == 0) {
          return 0L;
      }
      if (refCnt() == 0) {
          throw new IllegalReferenceCountException(0);
      }
      // Call open to make sure fc is initialized. This is a no-oop if we called it before.
      open();
      // 包装 FileChannel.transferTo 方法 Zero-Copy
      long written = file.transferTo(this.position + position, count, target);
      if (written > 0) {
          transferred += written;
      } else if (written == 0) {
          // If the amount of written data is 0 we need to check if the requested count is bigger then the
          // actual file itself as it may have been truncated on disk.
          //
          // See https://github.com/netty/netty/issues/8868
          validate(this, position);
      }
      return written;
  }

堆外内存

堆内内存,把内存对象分配在 Java 虚拟机的堆以外的内存,又称直接内存。
堆内内存 / 堆外内存的切换方式

  • 指定参数:io.netty.noPreferDirect = true / false
  • 默认不使用堆内内存的,可以这样指定使用堆内内存
ServerBootstrap b = new ServerBootstrap();
b.childOption(ChannelOption.ALLOCATOR, new PooledByteBufAllocator(false));

Netty 分配堆外内存的本质是调用 JDK 的ByteBuffer.allocateDirect(initialCapacity)方法,再往下就是 JDK 的 Unsafe 了。

内存池

内存池就是在程序启动时,预先向堆中申请一部分内存,交给一个管理对象。在程序运行中,需要时向管理对象“借”,不需要时“还”给管理对象。

  1. 常用开源实现 Apache Commons pool
  2. Netty 轻量级内存池 io.netty.util.Recycler

内存池 / 非内存池 的切换方式

  • 指定参数 :io.netty.allocator.type = unpooled / pooled
  • 启动类中指定配置:
ServerBootstrap b = new ServerBootstrap();
b.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);

ServerBootstrap b = new ServerBootstrap();
b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

Netty 默认使用什么类型呢?
我们查看 io.netty.channel.DefaultChannelConfig 类:

private volatile ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;

继续看 io.netty.buffer.ByteBufAllocator :

ByteBufAllocator DEFAULT = ByteBufUtil.DEFAULT_ALLOCATOR;

继续看 io.netty.buffer.ByteBufUtil :

static final ByteBufAllocator DEFAULT_ALLOCATOR;

  static {
      // 系统变量中取值,若为安卓平台则使用 unpooled
      String allocType = SystemPropertyUtil.get(
              "io.netty.allocator.type", PlatformDependent.isAndroid() ? "unpooled" : "pooled");
      allocType = allocType.toLowerCase(Locale.US).trim();

      ByteBufAllocator alloc;
      if ("unpooled".equals(allocType)) {
          alloc = UnpooledByteBufAllocator.DEFAULT;
          logger.debug("-Dio.netty.allocator.type: {}", allocType);
      } else if ("pooled".equals(allocType)) {
          alloc = PooledByteBufAllocator.DEFAULT;
          logger.debug("-Dio.netty.allocator.type: {}", allocType);
      } else {
      // 默认为内存池
          alloc = PooledByteBufAllocator.DEFAULT;
          logger.debug("-Dio.netty.allocator.type: pooled (unknown: {})", allocType);
      }

      DEFAULT_ALLOCATOR = alloc;

      THREAD_LOCAL_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalDirectBufferSize", 0);
      logger.debug("-Dio.netty.threadLocalDirectBufferSize: {}", THREAD_LOCAL_BUFFER_SIZE);

      MAX_CHAR_BUFFER_SIZE = SystemPropertyUtil.getInt("io.netty.maxThreadLocalCharBufferSize", 16 * 1024);
      logger.debug("-Dio.netty.maxThreadLocalCharBufferSize: {}", MAX_CHAR_BUFFER_SIZE);
  }

默认情况下,使用非池实现,其他平台使用内存池实现,在未指定 netty.allocator.type 参数时,默认内存池实现。
具体的内存池实现 io.netty.buffer.PooledDirectByteBuf
我们看一下 PooledDirectByteBuf#newInstance 方法:

private static final ObjectPool<PooledDirectByteBuf> RECYCLER = ObjectPool.newPool(
          new ObjectCreator<PooledDirectByteBuf>() {
      @Override
      public PooledDirectByteBuf newObject(Handle<PooledDirectByteBuf> handle) {
          return new PooledDirectByteBuf(handle, 0);
      }
  });

  static PooledDirectByteBuf newInstance(int maxCapacity) {
  // 从池中获取
      PooledDirectByteBuf buf = RECYCLER.get();
      buf.reuse(maxCapacity);
      return buf;
  }

这个 RECYCLER 就是 Netty 的 Recycler 实现,

public final T get() {
    if (maxCapacityPerThread == 0) {
        // 表示没有开启池化配置,new Object 返回
        return newObject((Handle<T>) NOOP_HANDLE);
    }
    // ThreadLocal 获取返回
    Stack<T> stack = threadLocal.get();
    DefaultHandle<T> handle = stack.pop();
    if (handle == null) {
        // 池中没有对象时新建
        handle = stack.newHandle();
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}

上面的 get 方法时借,所谓有借有还再借不难,再看一下归还的方法(Recycler 的内部类 DefaultHandle ):

@Override
public void recycle(Object object) {
   if (object != value) {
       throw new IllegalArgumentException("object does not belong to handle");
     }

   Stack<?> stack = this.stack;
   if (lastRecycledId != recycleId || stack == null) {
       throw new IllegalStateException("recycled already");
   }
   // 归还回内存池
    stack.push(this);
}

References:
引用链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算机硬件系统: 计算机硬件系统是构成计算机物理实体的所有部件的集合,包括核心组件以及外设。其主要组成部分包括: 中央处理单元 (CPU):作为计算机的大脑,负责执行指令、进行逻辑运算和数据处理。 内存:包括随机访问内存 (RAM) 和只读存储器 (ROM),用于临时或永久地存储程序和数据供CPU快速访问。 存储设备:如硬盘、固态硬盘 (SSD)、光盘驱动器等,用于长期保存大量的程序和数据。 输入/输出设备:如键盘、鼠标、显示器、打印机、扫描仪、摄像头等,实现人与计算机之间的交互以及数据的输入和输出。 主板:连接和协调各硬件组件工作,包含芯片组、扩展插槽、接口等。 其他外设:如声卡、网卡、显卡等,提供特定功能支持。 计算机软件系统: 软件系统是指在硬件之上运行的各种程序和数据的集合,分为两大类: 系统软件: 操作系统 (OS):如Windows、macOS、Linux、Unix等,是管理和控制计算机硬件与软件资源、提供公共服务、协调计算机各部分工作的基础平台,是用户与计算机硬件之间的桥梁。 驱动程序:为特定硬件设备提供接口,使操作系统能够识别和控制这些设备。 实用工具:如编译器、链接器、调试器、文件管理器等,协助开发、维护和管理计算机系统。 应用软件: 办公套件:如Microsoft Office、LibreOffice,包括文字处理、电子表格、演示文稿等工具。 专业软件:如AutoCAD(工程制图)、Adobe Creative Suite(图形设计与多媒体编辑)、MATLAB(数值计算与数据分析)等,针对特定行业或任务的专业应用。 互联网应用:如浏览器、电子邮件客户端、即时通讯软件、社交媒体平台等。 游戏:休闲游戏、网络游戏、模拟游戏等各类娱乐软件。 信息系统: 在企业、机构或组织中,信息系统是指由硬件、软件、人员、数据资源、通信网络等组成的,用于收集、处理、存储、分发和管理信息,以支持决策制定、业务运营和战略规划的系统。这类系统包括: 数据库管理系统 (DBMS):如Oracle、MySQL、SQL Server,用于创建、维护和查询结构化数据。 企业资源计划 (ERP):整合企业的财务、供应链、人力资源、生产等多方面管理功能的综合性信息系统。 客户关系管理 (CRM):用于管理与客户互动的全过程,提升销售、营销和服务效率。 供应链管理 (SCM):优化供应链流程,包括采购、库存、物流、分销等环节。 决策支持系统 (DSS):辅助决策者分析复杂问题,提供数据驱动的决策建议。 网络系统: 包括局域网 (LAN)、广域网 (WAN)、互联网 (Internet) 等,通过路由器、交换机、调制解调器等网络设备,以及通信协议(如TCP/IP),实现计算机之间的数据传输和资源共享。 分布式系统: 由多台计算机通过网络互相协作,共同完成一项任务的系统。分布式系统可以提供高可用性、可扩展性、负载均衡等优点,如云计算平台、分布式数据库、区块链系统等。 安全系统: 旨在保护计算机系统免受恶意攻击、未经授权访问、数据泄露等安全威胁的措施和工具,包括防火墙、入侵检测系统、防病毒软件、身份认证与访问控制机制、数据加密技术等。 综上所述,计算机领域的“系统”概念广泛涉及硬件架构、软件层次、信息管理、网络通信、分布式计算以及安全保障等多个方面,它们相互交织,共同构成了现代计算机技术的复杂生态系统。
假设你正在为一个重要的大型公司开发一款全新的任务关键型的应用程序。在第一次会议上,你得知该系统必须要能够扩展到支撑 100000 名并发用户,并且不能有任何的性能损失,这时所有的目光都投向了你。你会怎么说呢? 你可以自信地说:“当然,没问题。”然后留下一顿崇拜的目光离开,回来后悄悄打开netty相关知识,开始编程。 什么是NettyNetty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。如果你还得去看这些专业术语 那我这文章就毫无意义,接下来我就用大白话告诉你什么是Netty和他的各种组件。 总所周知,Netty是网络编程方面的东西。Netty就是NIO技术的一套框架,就是对NIO技术的封装。有NIO,自然有IO,大白话就是一个是非阻塞IO,一个是阻塞IO。 早期时候的网络编程是阻塞IO模式,IO就是输入输出,举个例子:你打字就是键盘的输入,输入你要打的东西。阻塞就是类似塞车,总是得卡在半路上,等着前面的车开走了我才能开,这里例子中就是电脑总得等着你输入完 我才能把你输入的东西发给别人。那么是什么在等?就是线程! 阻塞IO模式就是一个客户端连接到服务端,服务端就为每个新的客户端 Socket 创建一个新的 Thread。 Netty的书我帮你看!---什么是Netty 什么是Socket?Socket就是你想用Java代码API去组织数据,指定协议去通讯,很烦!写的太多代码!Socket帮你封装了 你只需要调用一下Socket的方法就行了,省事!回到图1-1,你会发现线程开太多了 ,100000人连接我开100000线程吗,还是一直阻塞的等待用户输入,我开的线程还不能去干其他事,那还叫线程吗?太丢人了! 所以Java推出了NIO,Java 对于非阻塞 I/O 的支持是在 2002 年引入的,位于 JDK 1.4 的 java.nio 包中。解决我刚才说的弊端思路就是如下图 Netty的书我帮你看!---什么是Netty class java.nio.channels.Selector 是 Java 的非阻塞 I/O 实现的关键。它使用了事件通知 API 以确定在一组非阻塞套接字中有哪些已经就绪能够进 行 I/O 相关的操作。继续上面的例子,就是当有人输入了 通过Selector 告诉线程说某个Socket有操作,赶紧处理下,这样就不用开那么多线程了,而且所有Socket都没操作的时候,这个线程还能去干别的事,多自由 这就是书本上说的总体来看,与阻塞 I/O 模型相比,这种模型提供了更好的资源管理: 1.使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销; 2.当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务。 可当你想使用NIO的时候发现,哇!还是很麻烦!需要记这么多东西,敲这么多代码,这时候Netty就孕育而生了!!在网络编程领域,NettyJava的卓越框架。它驾驭了Java高级API的能力,并将其隐藏在一 个易于使用的API之后。Netty使你可以专注于自己真正感兴趣的——你的应用程序的独一无二的价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值