Netty Review - 直接内存的应用及源码分析

文章目录

  • Pre
  • 概述
  • 应用
    • 访问效率: 堆内存 VS 直接内存
    • 申请效率: 堆内存 VS 直接内存
    • 数据存储结构: 堆内存 VS 直接内存
    • 结论
  • ByteBuffer.allocateDirect 源码分析
    • unsafe.allocateMemory(size) ---> C++方法
  • JVM参数 -XX:MaxDirectMemorySize
  • 直接内存如何管理?
    • 理论
    • Code
  • 总结
    • 优点
    • 缺点

在这里插入图片描述

在这里插入图片描述


Pre

Netty Review - ServerBootstrap源码解析

Netty Review - NioServerSocketChannel源码分析

Netty Review - 服务端channel注册流程源码解析


概述

在Java中,数据通常存储在堆内存中。Java里用DirectByteBuffer可以分配一块直接内存(堆外内存),元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。

在这里插入图片描述

但是,在某些情况下,直接操作系统的本地内存(off-heap memory)可能更有利,特别是对于需要进行大量I/O操作的应用程序,比如网络应用程序。Netty是一个用于构建高性能网络应用程序的框架,它提供了对直接内存的支持,以便更有效地处理数据传输。

直接内存的主要优势在于它的分配和释放不受Java堆内存管理的影响,因此可以避免堆内存的垃圾回收开销。由于直接内存是在操作系统层面分配和释放的,因此它不受Java虚拟机的堆内存大小限制,可以更灵活地管理大量的数据。


在Netty中,直接内存通常用于存储网络数据,例如接收到的字节数据或要发送的字节数据。通过使用直接内存,Netty能够更有效地进行数据传输,减少了数据在Java堆内存和操作系统内存之间的复制操作,提高了数据传输的效率和性能

为了有效地管理直接内存的分配和释放,Netty使用了内存池(Memory Pool)的概念。通过内存池,Netty可以重用已分配的直接内存,避免频繁地进行内存分配和释放操作,减少了系统的内存管理开销,并提高了系统的稳定性和可靠性。

总而言之,Netty的直接内存支持使得开发人员能够构建高性能、高效率的网络应用程序,通过更有效地利用操作系统的本地内存,提高了数据传输的速度和性能,同时降低了系统的内存管理开销。


应用

访问效率: 堆内存 VS 直接内存

package com.artisan.directbuffer;import java.nio.ByteBuffer;/*** 直接内存与堆内存的区别* @author artisan*/
public class DirectMemoryTest {public static void heapAccess() {long startTime = System.currentTimeMillis();//分配堆内存ByteBuffer buffer = ByteBuffer.allocate(1000);for (int i = 0; i < 100000; i++) {for (int j = 0; j < 200; j++) {buffer.putInt(j);}buffer.flip();for (int j = 0; j < 200; j++) {buffer.getInt();}buffer.clear();}long endTime = System.currentTimeMillis();System.out.println("堆内存访问:" + (endTime - startTime) + "ms");}public static void directAccess() {long startTime = System.currentTimeMillis();//分配直接内存ByteBuffer buffer = ByteBuffer.allocateDirect(1000);for (int i = 0; i < 100000; i++) {for (int j = 0; j < 200; j++) {buffer.putInt(j);}buffer.flip();for (int j = 0; j < 200; j++) {buffer.getInt();}buffer.clear();}long endTime = System.currentTimeMillis();System.out.println("直接内存访问:" + (endTime - startTime) + "ms");}public static void main(String args[]) {for (int i = 0; i < 5; i++) {heapAccess();directAccess();}}
}

在这里插入图片描述


申请效率: 堆内存 VS 直接内存

package com.artisan.directbuffer;import java.nio.ByteBuffer;/*** 直接内存与堆内存的区别* @author artisan*/
public class DirectMemoryTest {public static void heapAllocate() {long startTime = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {ByteBuffer.allocate(100);}long endTime = System.currentTimeMillis();System.out.println("堆内存申请:" + (endTime - startTime) + "ms");}public static void directAllocate() {long startTime = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {ByteBuffer.allocateDirect(100);}long endTime = System.currentTimeMillis();System.out.println("直接内存申请:" + (endTime - startTime) + "ms");}public static void main(String args[]) {for (int i = 0; i < 5; i++) {heapAllocate();directAllocate();}}
}

在这里插入图片描述


数据存储结构: 堆内存 VS 直接内存

在这里插入图片描述

在这里插入图片描述


结论

优点:

  • 不占用堆内存空间,减少了发生GC的可能
  • java虚拟机实现上,本地IO会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)

缺点:

  • 初始分配较慢
  • 没有JVM直接帮助管理内存,容易发生内存溢出。为了避免一直没有FULL GC,最终导致直接内存把物理内存耗完。我们可以指定直接内存的最大值,通过-XX:MaxDirectMemorySize来指定,当达到阈值的时候,调用system.gc来进行一次FULL GC,间接把那些没有被使用的直接内存回收掉

从程序运行结果看出直接内存申请较慢,但访问效率高。在java虚拟机实现上,本地IO一般会直接操作直接内存(直接内存=>系统调用=>硬盘/网卡),而非直接内存则需要二次拷贝(堆内存=>直接内存=>系统调用=>硬盘/网卡)。

直接内存在申请时可能会比在堆内存中分配的速度慢一些,这是因为在堆内存中分配只涉及Java堆内存管理系统的操作,而在直接内存中分配则涉及到操作系统的系统调用,因此可能会有更多的开销。但是,一旦分配完成,直接内存的访问效率通常会比堆内存高,因为它可以直接被操作系统访问,而不需要经过Java堆内存管理系统的复制操作。

在Java虚拟机的实现中,对于本地IO操作,如果使用直接内存,则可以直接操作直接内存,然后通过系统调用将数据传输到硬盘或网卡。这样可以避免额外的内存复制操作,提高了IO操作的效率。而对于堆内存中的数据,需要先将数据复制到直接内存中,然后再进行系统调用传输到硬盘或网卡,这就需要进行额外的数据拷贝,导致了额外的开销和性能损失。

因此,对于需要进行大量IO操作的应用程序,使用直接内存通常能够获得更好的性能表现。然而,直接内存的使用也需要注意管理和释放,以避免内存泄漏和其他潜在的问题。


ByteBuffer.allocateDirect 源码分析

public static ByteBuffer allocateDirect(int capacity) {if (capacity < 0)throw new IllegalArgumentException();return new DirectByteBuffer(capacity);
}

这是DirectByteBuffer类的构造函数实现,用于创建直接字节缓冲区对象。

DirectByteBuffer(int cap) { // 包私有super(-1, 0, cap, cap); // 调用父类构造函数,设置position和limit为0,capacity为cap// 判断是否需要在直接内存页对齐boolean pa = VM.isDirectMemoryPageAligned();// 获取操作系统页面大小int ps = Bits.pageSize();// 计算需要分配的内存大小,如果需要页对齐,则在容量上加上一个页面大小long size = Math.max(1L, (long)cap + (pa ? ps : 0));//判断是否有足够的直接内存空间分配,可通过‐XX:MaxDirectMemorySize=<size>参数指定直接内存最大可分配空间,如果不指定默认为最大堆内存大小,//在分配直接内存时如果发现空间不够会显示调用System.gc()触发一次full gc回收掉一部分无用的直接内存的引用对象,同时直接内存也会被释放掉. 如果释放完分配空间还是不够会抛出异常java.lang.OutOfMemoryError// 预留直接内存,如果分配失败,抛出OutOfMemoryErrorBits.reserveMemory(size, cap);long base = 0;try {// 分配内存,并返回分配的内存地址base = unsafe.allocateMemory(size);} catch (OutOfMemoryError x) {// 如果分配失败,释放预留的内存,并抛出异常Bits.unreserveMemory(size, cap);throw x;}// 初始化分配的内存为0unsafe.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// 如果需要页对齐,并且分配的内存地址不在页面边界上,将地址向上舍入到页面边界address = base + ps - (base & (ps - 1));} else {address = base;}// 创建Cleaner对象,用于释放内存// 使用Cleaner机制注册内存回收处理函数,当直接内存引用对象被GC清理掉时,会提前调用这里注册的释放直接内存的Deallocator线程对象的run方法cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;
}

该构造函数执行以下操作:

  1. 调用父类构造函数,初始化ByteBuffer的position和limit为0,capacity为cap。
  2. 判断是否需要进行直接内存页对齐。
  3. 获取操作系统的页面大小。
  4. 计算需要分配的内存大小,如果需要页对齐,则在容量上加上一个页面大小。
  5. 预留直接内存。
  6. 分配内存,并返回分配的内存地址。
  7. 初始化分配的内存为0。
  8. 如果需要进行页对齐,并且分配的内存地址不在页面边界上,将地址向上舍入到页面边界。
  9. 创建Cleaner对象,用于释放内存。
  10. 最后,att字段设置为null,该字段用于存储可选的附件对象。

unsafe.allocateMemory(size) —> C++方法

UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))// 使用UnsafeWrapper宏定义的函数进行包装UnsafeWrapper("Unsafe_AllocateMemory");// 将传入的jlong类型的size转换为size_t类型的sz,这是为了在C++中使用size_t sz = (size_t)size;// 检查size是否为负数或超出了jlong类型的范围,如果是,则抛出IllegalArgumentException异常if (sz != (julong)size || size < 0) {THROW_0(vmSymbols::java_lang_IllegalArgumentException());}// 如果size为0,则直接返回0,不进行内存分配if (sz == 0) {return 0;}// 将sz向上舍入到HeapWordSize的倍数,确保内存对齐sz = round_to(sz, HeapWordSize);// 调用os::malloc函数分配内存。os::malloc是JVM调用操作系统的内存分配函数,通常是mallocvoid* x = os::malloc(sz, mtInternal);// 如果内存分配失败(返回NULL),则抛出OutOfMemoryError异常if (x == NULL) {THROW_0(vmSymbols::java_lang_OutOfMemoryError());}// 将分配的内存地址转换为Java对象引用并返回return addr_to_java(x);
UNSAFE_END

这段代码是在C++中实现的Unsafe_AllocateMemory函数, 通过Unsafe类的allocateMemory方法在本地堆上分配内存的底层实现。


JVM参数 -XX:MaxDirectMemorySize

在Java应用的默认情况下,可以使用的最大直接内存取决于系统的限制和JVM参数的设置。一般来说,默认情况下,JVM并不限制直接内存的使用,但是操作系统可能会对进程的虚拟内存大小进行限制。

在某些操作系统上,进程可以使用的虚拟内存大小受到32位或64位系统的限制,以及特定操作系统的配置和限制。另外,有些操作系统还可能会限制单个进程可分配的直接内存的大小。

通常情况下,如果没有显式设置直接内存的大小(例如通过-XX:MaxDirectMemorySize参数),Java应用程序可以使用的最大直接内存大小与堆内存大小没有直接关系。默认情况下,Java应用程序可以根据操作系统和硬件配置使用尽可能多的直接内存,但是受到操作系统的限制。

也有一种说法,默认能够使用的最大的直接内存 = 最大堆内存。 待考证。


直接内存如何管理?

理论

ByteBuffer.allocateDirect() 方法用于申请直接内存,这种内存是由操作系统分配的,而不是由JVM的堆内存管理器分配的。因此,直接内存的管理和回收与堆内存不同。

直接内存的管理和回收通常由操作系统来完成。当调用 allocateDirect() 方法时,会向操作系统请求一块内存区域。这个区域的分配和释放由操作系统的内存管理机制来管理,而不是由JVM的垃圾回收器来管理。

直接内存的释放不是由 Java 的垃圾回收器来处理的,而是由操作系统的内存管理机制来处理。当不再需要直接内存时,ByteBuffer 对象可以被垃圾回收器回收,但是直接内存本身并不会被立即释放。相反,直接内存的释放可能会延迟到 JVM 关闭时,或者在应用程序调用 System.gc() 进行垃圾回收时。

另外,可以使用 ByteBufferclear() 方法或者手动调用 System.gc() 来提示 JVM 尽快释放直接内存。但是这并不能保证直接内存会立即被释放,因为直接内存的释放时间是由操作系统来决定的。


Code

一个简单的案例和代码实现,演示了如何手动管理直接内存的分配和释放

package com.artisan.directbuffer;import java.nio.ByteBuffer;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class DirectMemoryManager {private ByteBuffer buffer;public DirectMemoryManager(int capacity) {// 申请直接内存buffer = ByteBuffer.allocateDirect(capacity);}// 使用直接内存进行读写操作public void writeToBuffer(byte[] data) {buffer.put(data);}public byte[] readFromBuffer() {// 将缓冲区的位置设为 0,限制设为当前位置buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);return data;}// 释放直接内存public void releaseMemory() {// 释放 buffer 对象buffer.clear();// 将 buffer 设置为 null,便于垃圾回收buffer = null;// 强制调用垃圾回收器进行内存回收System.gc();}public static void main(String[] args) {// 创建直接内存管理器并分配直接内存DirectMemoryManager memoryManager = new DirectMemoryManager(1024);// 使用直接内存进行读写操作byte[] data = "Hello, Direct Memory!".getBytes();memoryManager.writeToBuffer(data);byte[] readData = memoryManager.readFromBuffer();System.out.println("Read from buffer: " + new String(readData));// 释放直接内存memoryManager.releaseMemory();}
}

在这个例子中,DirectMemoryManager 类负责管理直接内存。它通过调用 ByteBuffer.allocateDirect(capacity) 方法来申请直接内存,并通过 writeToBuffer()readFromBuffer() 方法进行读写操作。

最后,通过调用 releaseMemory() 方法释放直接内存。

在释放直接内存时,首先调用 buffer.clear() 方法清空缓冲区,然后将 buffer 对象置为 null,最后强制调用 System.gc() 方法触发垃圾回收。

请注意,这并不能保证直接内存会立即被释放,因为直接内存的释放时间是由操作系统来决定的。

在这里插入图片描述


总结

优点

  1. 减少了垃圾回收的影响: 直接内存不受 Java 堆内存大小的限制,可以避免频繁的垃圾回收,提高应用的性能和稳定性。

  2. 减少了数据拷贝: 直接内存与操作系统进行了直接交互,减少了数据在 Java 堆内存与操作系统内存之间的拷贝次数,提高了 I/O 操作的效率。

缺点

  1. 初始分配较慢: 直接内存的分配过程通常比堆内存的分配要慢,因为它需要调用操作系统的原生方法来申请内存空间。

  2. 内存管理复杂: 直接内存的管理需要手动进行,没有 JVM 自动进行内存回收的机制,容易导致内存泄漏或者内存溢出的问题。

  3. 容易导致内存溢出: 如果不合理地使用直接内存,可能会导致操作系统的物理内存被耗尽,从而引发应用程序的崩溃。

  4. 难以调试: 直接内存的内存溢出问题比较难以调试和定位,需要借助一些专业的内存分析工具来进行排查。

总的来说,直接内存适用于需要频繁进行 I/O 操作或者数据量较大的场景,但需要开发人员在使用时注意合理管理和控制直接内存的大小,避免出现性能问题或者内存溢出的情况。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/258262.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

隐函数的求导【高数笔记】

1. 什么是隐函数&#xff1f; 2. 隐函数的做题步骤&#xff1f; 3. 隐函数中的复合函数求解法&#xff0c;与求导中复合函数求解法有什么不同&#xff1f; 4. 隐函数求导的过程中需要注意什么&#xff1f;

Mysql运维篇(四) Xtarbackup--备份与恢复练习

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; 前言 xtrabackup是Percona公司CTO Vadim参与开发的一款基于InnoDB的在线热备工具&#xff0c;具有…

164基于matlab的奇异值分解、小波降噪、zoom细化

基于matlab的奇异值分解、小波降噪、zoom细化。程序已调通&#xff0c;可直接运行。 164 奇异值分解 小波降噪 zoom细化 (xiaohongshu.com)

Spring AMQP(3.1.1)设置ConfirmCallback和ReturnsCallback

文章目录 一、起因二、代码1. 定义exchange和queue2. RabbitTemplate3. EnhancedCorrelationData4. 发送消息 环境如下 VersionSpringBoot3.2.1spring-amqp3.1.1RabbitMq3-management 一、起因 老版本的spring-amqp在CorrelationData上设置ConfirmCallback。但是今天却突然发…

Python访问数据库

目录 SQLite数据库 SQLite数据类型 Python数据类型与SQLite数据类型的映射 使用GUI管理工具管理SQLite数据库 数据库编程的基本操作过程 sqlite3模块API 数据库连接对象Connection 游标对象Cursor 数据库的CRUD操作示例 示例中的数据表 无条件查询 有条件查询 插入…

【Java多线程】对进程与线程的理解

目录 1、进程/任务&#xff08;Process/Task&#xff09; 2、进程控制块抽象(PCB Process Control Block) 2.1、PCB重要属性 2.2、PCB中支持进程调度的一些属性 3、 内存分配 —— 内存管理&#xff08;Memory Manage&#xff09; 4、线程&#xff08;Thread&#xff09;…

Unity(单元测试)在STM32上的移植与应用

概述 Unity Test是一个为C构建的单元测试框架。本文基于STM32F407为基础&#xff0c;完全使用STM32CubeIDE进行开发&#xff0c;移植和简单使用Unity。 单片机型号&#xff1a;STM32F407VET6 软件&#xff1a;STM32CubeIDE Version: 1.14.1 Unity Version&#xff1a;2.…

相机图像质量研究(22)常见问题总结:CMOS期间对成像的影响--光学串扰

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

LeetCode、338. 比特位计数【简单,位运算】

文章目录 前言LeetCode、338. 比特位计数【中等&#xff0c;位运算】题目链接与分类思路位运算移位处理前缀思想实现 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java…

快速学习Spring

Spring 简介 Spring 是一个开源的轻量级、非侵入式的 JavaEE 框架&#xff0c;它为企业级 Java 应用提供了全面的基础设施支持。Spring 的设计目标是简化企业应用的开发&#xff0c;并解决 Java 开发中常见的复杂性和低效率问题。 Spring常用依赖 <dependencies><!-…

OpenCV入门:图像处理的基石

在数字图像处理领域&#xff0c;OpenCV&#xff08;开源计算机视觉库&#xff09;是一个不可或缺的工具。它包含了一系列强大的算法和函数&#xff0c;使得开发者可以轻松地处理图像和视频数据。本文将带你走进OpenCV的世界&#xff0c;了解其基本概念和常见应用。 1. OpenCV简…

【51单片机】DS18B20(江科大)

一、DS18B20温度传感器 1.DS18B20介绍 DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点 测温范围 :- 55℃到125℃ 通信接口:1-Wire(单总线) 其它特征:可形成…

Crypto--RSA

题目&#xff1a;RSA&#xff08;BUUCTF-CRYPTO-RSABUUCTF在线评测&#xff09; 解题过程&#xff1a;直接利用RSAtool工具&#xff08;RSA-Tool 2(RSA算法辅助工具)V1.7绿色版下载 - 下载吧 (xiazaiba.com)&#xff09;解题。

数据结构(4) 链表(链式存储)

链表&#xff08;链式存储&#xff09; 单链表定义基本操作的实现单链表的插入按位序插入指定节点的前插指定节点的后插 单链表的删除 小结 单链表 定义 顺序表优点:可随机存取&#xff0c;存储密度高&#xff0c;缺点:要求大片连续空间&#xff0c;改变容量不方便。 单链表优…

云计算基础-虚拟化概述

虚拟化概述 虚拟化是一种资源管理技术&#xff0c;能够将计算机的各种实体资源&#xff08;如CPU、内存、磁盘空间、网络适配器等&#xff09;予以抽象、转换后呈现出来并可供分割、组合为一个或多个逻辑上的资源。这种技术通过在计算机硬件上创建一个抽象层&#xff0c;将单台…

人工智能学习与实训笔记(七):神经网络之推荐系统处理

九、模型压缩与知识蒸馏 出于对响应速度&#xff0c;存储大小和能耗的考虑&#xff0c;往往需要对大模型进行压缩。 模型压缩方法主要可以分为以下四类&#xff1a; 参数修剪和量化&#xff08;Parameter pruning and quantization&#xff09;&#xff1a;用于消除对模型表…

蓝桥省赛真题|简单:分数

题目链接&#xff1a;https://www.lanqiao.cn/problems/610/learning/?page1&first_category_id1&second_category_id3&tags2018&name%E5%88%86%E6%95%B0 题不难&#xff0c;但是可以帮助编程时好的习惯的养成&#xff0c;更加注意一些细节。 注意几个地方︰…

机器人专题:我国机器人产业园区发展现状、问题、经验及建议

今天分享的是机器人系列深度研究报告&#xff1a;《机器人专题&#xff1a;我国机器人产业园区发展现状、问题、经验及建议》。 &#xff08;报告出品方&#xff1a;赛迪研究院&#xff09; 报告共计&#xff1a;26页 机器人作为推动工业化发展和数字中国建设的重要工具&…

【RT-DETR有效改进】利用EMAttention加深网络深度提高模型特征提取能力(特征选择模块)

一、本文介绍 本文给大家带来的改进机制是EMAttention注意力机制&#xff0c;它的核心思想是&#xff0c;重塑部分通道到批次维度&#xff0c;并将通道维度分组为多个子特征&#xff0c;以保留每个通道的信息并减少计算开销。EMA模块通过编码全局信息来重新校准每个并行分支中…

【Qt】环境安装与初识

目录 一、Qt背景介绍 二、搭建Qt开发环境 三、新建工程 四、Qt中的命名规范 五、Qt Creator中的快捷键 六、QWidget基础项目文件详解 6.1 .pro文件解析 6.2 widget.h文件解析 6.3 widget.cpp文件解析 6.4 widget.ui文件解析 6.5 main.cpp文件解析 七、对象树 八、…