当前位置: 首页 > news >正文

【操作系统】HeapByteBuffer和DirectByteBuffer的区别

DirectByteBuffer和HeapByteBuffer是Java NIO中ByteBuffer的两种实现方式。

HeapByteBuffer是在Java堆上分配的字节缓冲区,它使用数组来存储数据。HeapByteBuffer的优点是它具有良好的兼容性和可移植性,且在大多数情况下性能表现良好。它适用于大部分的应用场景,并且在内存管理方面具有更好的可控性和可调优性。

DirectByteBuffer是在直接内存中分配的字节缓冲区,它不是使用Java堆上的数组来存储数据,而是使用JNI(Java Native Interface)来直接操作本地内存。DirectByteBuffer的优点是它可以提供更高的IO性能,因为数据可以直接从内核缓冲区复制到DirectByteBuffer中,避免了数据的复制。它适用于处理大量的数据、网络数据传输、以及需要与本地库进行高效交互的场景。

堆内内存与堆外内存

堆内内存(Heap Memory)和堆外内存(Off-Heap Memory)是指在JAVA中两种不同的内存分配方式,其中的堆指的是JVM中的堆。

堆内内存:

  • 堆内内存是指在Java堆中分配的内存。在Java中,对象的创建和销毁都是在堆内存中进行的。堆内内存由Java虚拟机(JVM)的垃圾回收机制进行管理,即通过垃圾回收器自动进行内存回收。

  • 堆内内存具有自动内存管理的特性,程序员不需要手动释放内存。Java的堆内内存使用的是Java堆中的对象引用,通过引用计数和可达性分析等机制进行垃圾回收。

堆外内存:

  • 堆外内存是指在Java堆之外分配的内存,也称为直接内存(Direct Memory)。

  • 堆外内存通常由本地操作系统管理,不受JVM垃圾回收的控制。程序员需要手动释放这部分内存,一般通过Java NIO中的ByteBuffer的cleaner()方法或显式调用free()方法来释放。

  • 堆外内存的分配和释放并不会受到Java堆内存的限制,可以提供更大的内存空间。

  • 堆外内存适用于需要更高的IO性能、处理大量数据或需要与本地库进行高效交互的场景。在网络编程、数据库操作、图形处理等领域常常使用堆外内存。

需要注意的是,堆外内存的分配和释放需要谨慎操作,因为它不受Java堆内存的自动管理,容易导致内存泄漏和内存溢出等问题。因此,在使用堆外内存时,需要合理地管理内存的生命周期。

HeapByteBuffer

HeapByteBuffer主要用于在JVM堆内存中分配和操作字节。

HeapByteBuffer的使用如下:

package com.morris.io;import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;/*** HeapByteBuffer的使用*/
public class HeapByteBufferDemo {public static void main(String[] args) throws IOException {ByteBuffer byteBuffer = ByteBuffer.allocate(1024);byteBuffer.put("hello".getBytes(StandardCharsets.UTF_8));byteBuffer.flip();FileOutputStream fos = new FileOutputStream("HeapByteBufferDemo.txt");FileChannel channel = fos.getChannel();channel.write(byteBuffer);channel.close();fos.close();}
}

产生的系统调用如下:

openat(AT_FDCWD, "HeapByteBufferDemo.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
write(4, "hello", 5)                    = 5
close(4)

HeapByteBuffer的内部实现通过使用数组来存储数据,可以在HeapByteBuffer的构造方法中看到数组的创建:

HeapByteBuffer(int cap, int lim) {            // package-private// 创建了一个字节数组super(-1, 0, lim, cap, new byte[cap], 0);
}

DirectByteBuffer

DirectByteBuffer主要用于在操作系统的内存中分配和操作字节。与HeapByteBuffer不同,DirectByteBuffer直接在操作系统的内存中分配数据。

DirectByteBuffer的使用如下:

package com.morris.io;import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;/*** DirectByteBuffer的使用*/
public class DirectByteBufferDemo {public static void main(String[] args) throws IOException {System.in.read();ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*128);byteBuffer.put("hello".getBytes(StandardCharsets.UTF_8));byteBuffer.flip();System.in.read();FileOutputStream fos = new FileOutputStream("DirectByteBufferDemo.txt");FileChannel channel = fos.getChannel();channel.write(byteBuffer);channel.close();fos.close();}
}

产生的系统调用如下:

mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fdb8c000000
openat(AT_FDCWD, "DirectByteBufferDemo.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
fstat(4, {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
write(4, "hello", 5)                    = 5
close(4)

我们在应用程序中申请了128KB(128*1024=131072B)的内存,底层会使用mmap系统调用在进程的堆内分配一块132KB的内存,这块内存是在进程的堆内,JVM的堆外

在上面的例子中,我们申请了128KB的内存,实际上分配一块132KB的内存,为什么会多分配4KB呢?我也没明白?

DirectByteBuffer的构造方法如下:

DirectByteBuffer(int cap) {                   // package-privatesuper(-1, 0, cap, cap);boolean pa = VM.isDirectMemoryPageAligned();int ps = Bits.pageSize();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.setMemory(base, size, (byte) 0);if (pa && (base % ps != 0)) {// Round up to page boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;
}

我们可以看到DirectByteBuffer底层是使用unsafe.allocateMemory来申请堆外内存,这是一个本地方法。

实际上我们自己也可以借助unsafe来申请堆外内存:

package com.morris.io;import sun.misc.Unsafe;import java.io.IOException;
import java.lang.reflect.Field;/*** 使用unsafe来申请内存*/
public class UnsafeAllocateMemoryDemo {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {Unsafe unsafe = getUnsafe();long address = unsafe.allocateMemory(128 * 1024);unsafe.freeMemory(address);}public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);return (Unsafe) field.get(null);}
}

产生的系统调用如下:

mmap(NULL, 135168, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe07801c000
munmap(0x7fe07801c000, 135168)          = 0

当使用mmap系统调用申请到堆外内存空间后,可以使用munmap系统调用来释放这块内存。

为什么要使用DirectByteBuffer?

从上面的例子可以看到DirectByteBuffer在使用的过程中会比HeapByteBuffer多一次系统调用,性能不如HeapByteBuffer,为什么性能差却还使用DirectByteBuffer呢?

看源码,这里以FileChannel实现类FileChannelImpl.read中为例:

sun.nio.ch.FileChannelImpl#write(java.nio.ByteBuffer)

public int write(ByteBuffer var1) throws IOException {this.ensureOpen();if (!this.writable) {throw new NonWritableChannelException();} else {synchronized(this.positionLock) {int var3 = 0;int var4 = -1;byte var5;try {this.begin();var4 = this.threads.add();if (this.isOpen()) {do {var3 = IOUtil.write(this.fd, var1, -1L, this.nd);} while(var3 == -3 && this.isOpen());int var12 = IOStatus.normalize(var3);return var12;}var5 = 0;} finally {this.threads.remove(var4);this.end(var3 > 0);assert IOStatus.check(var3);}return var5;}}
}

然后会调用IOUtil.write():

static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {if (var1 instanceof DirectBuffer) {return writeFromNativeBuffer(var0, var1, var2, var4);} else {int var5 = var1.position();int var6 = var1.limit();assert var5 <= var6;int var7 = var5 <= var6 ? var6 - var5 : 0;ByteBuffer var8 = Util.getTemporaryDirectBuffer(var7);int var10;try {var8.put(var1);var8.flip();var1.position(var5);int var9 = writeFromNativeBuffer(var0, var8, var2, var4);if (var9 > 0) {var1.position(var5 + var9);}var10 = var9;} finally {Util.offerFirstTemporaryDirectBuffer(var8);}return var10;}
}

当向FileChannel中写数据,数据来源是DirectByteBuffer时,可以直接将DirectByteBuffer中的数据写入到文件中。

当向FileChannel中写数据,数据来源是HeapByteBuffer时,需要先将数据从HeapByteBuffer写入到一个临时的DirectByteBuffer中,再将临时DirectByteBuffer中的数据写入到文件中。

所以,当java程序数据需要频繁与本地io(本地磁盘、socket传输数据时),使用HeapByteBuffer读取时要多复制一次数据(即从DirectByteBuffer再复制到heapByteBuffer)。再加上写数据到本地时,又要再从HeapByteBuffer复制到另一个DirectByteBuffer,多了两次复制。

HeapByteBuffer底层其实是java的字节数组,而java字节数组是一个java对象,对象的内存是由jvm的堆进行管理的,那么不可避免的是GC时年轻代的eden、suvivor到老年代的各种复制以及回收。当字节数组比较小的时候还好说,如果是大对象,那么对于jvm的GC来说是一个很大的负担。而使用DirectByteBuffer,则是把字节数组交给操作系统管理(堆外内存),就可以极大的减少jvm的负担了。

DirectByteBuffer和HeapByteBuffer的使用场景

什么情况下使用DirectByteBuffer?

  • 频繁的native IO,即java程序与本地磁盘、socket传输数据。

  • 不需要经常创建和销毁DirectByteBuffer对象,有系统调用代价大,可以使用池复用DirectByteBuffer

  • DirectByteBuffer不会占用堆内存。也就是不会受到堆大小限制,只在DirectByteBuffer对象被回收后才会释放该缓冲区。

  • 大文件造成大对象,对GC负担比较重的情况

什么情况下使用HeapByteBuffer?其实除了上述的DirectByteBuffer使用场景之外的,基本可以用HeapByteBuffer。

  • 数据仅在java程序中流转传输,不与本地进行IO,例如Netty的责任链中传递对象可以使用HeapByteBuffer

  • 容量低,对GC负担低。快速回收

相关文章:

【操作系统】HeapByteBuffer和DirectByteBuffer的区别

DirectByteBuffer和HeapByteBuffer是Java NIO中ByteBuffer的两种实现方式。 HeapByteBuffer是在Java堆上分配的字节缓冲区&#xff0c;它使用数组来存储数据。HeapByteBuffer的优点是它具有良好的兼容性和可移植性&#xff0c;且在大多数情况下性能表现良好。它适用于大部分的…...

C++并发编程 -2.线程间共享数据

本章就以在C中进行安全的数据共享为主题。避免上述及其他潜在问题的发生的同时&#xff0c;将共享数据的优势发挥到最大。 一. 锁分类和使用 按照用途分为互斥、递归、读写、自旋、条件变量。本章节着重介绍前四种&#xff0c;条件变量后续章节单独介绍。 由于锁无法进行拷贝…...

Kubernetes-资源清单

一、k8s中的资源 什么是资源清单 我们跟kubernetes集群进行交互的时候&#xff0c;我们需要给K8S集群传输数据&#xff0c;传输信息&#xff0c;K8S才能按照我们的要求来运行&#xff0c;这个传输的文件&#xff0c;基本上都会通过资源清单进行传递。资源清单是我们跟集群进行…...

ABAP 笔记--内表结构不一致,无法更新数据库MODIFY和UPDATE

目录 ABAP 笔记内表结构不一致&#xff0c;无法更新数据库MODIFY和UPDATE ABAP 笔记 内表结构不一致&#xff0c;无法更新数据库 MODIFY和UPDATE 如果是使用MODIFY或者UPDATE...

机器学习-3降低损失(Reducing Loss)

机器学习-3降低损失(Reducing Loss) 学习内容来自&#xff1a;谷歌ai学习 https://developers.google.cn/machine-learning/crash-course/framing/check-your-understanding?hlzh-cn 本文作为学习记录1.降低损失&#xff1a;迭代方法 迭代学习 下图展示了机器学习算法用于训…...

蓝桥杯备战(AcWing算法基础课)-高精度-减-高精度

目录 前言 1 题目描述 2 分析 2.1 第一步 2.2 第二步 3 代码 前言 详细的代码里面有自己的理解注释 1 题目描述 给定两个正整数&#xff08;不含前导 00&#xff09;&#xff0c;计算它们的差&#xff0c;计算结果可能为负数。 输入格式 共两行&#xff0c;每行包含一…...

AspNet web api 和mvc 过滤器差异

最近在维护老项目。定义个拦截器记录接口日志。但是发现不生效 最后发现因为继承的 ApiController不是Controller 只能用 System.Web.Http下的拦截器生效。所以现在总结归纳一下 Web Api: System.Web.Http.Filters.ActionFilterAttribute 继承该类 Mvc: System.Web.Mvc.Ac…...

HarmonyOS应用/服务发布:打造多设备生态的关键一步

目前 前言HarmonyOS 应用/服务发布的重要性使用HarmonyOS 构建跨设备的应用生态前期准备工作简述发布流程生成签名文件配置签名信息编译构建.app文件上架.app文件到AGC结束语 前言 随着智能设备的快速普及和多样化&#xff0c;以及编程语言的迅猛发展&#xff0c;构建一个无缝…...

【数据结构】双向带头循环链表实现及总结

简单不先于复杂&#xff0c;而是在复杂之后。 文章目录 1. 双向带头循环链表的实现2. 顺序表和链表的区别 1. 双向带头循环链表的实现 List.h #pragma once #include <stdio.h> #include <assert.h> #include <stdlib.h> #include <stdbool.h>typede…...

创建自己的Hexo博客

目录 一、Github新建仓库二、支持环境安装Git安装Node.js安装Hexo安装 三、博客本地运行本地hexo文件初始化本地启动Hexo服务 四、博客与Github绑定建立SSH密钥&#xff0c;并将公钥配置到github配置Hexo与Github的联系检查github链接访问hexo生成的博客 一、Github新建仓库 登…...

音箱、功放播放HDMI音频解决方案之HDMI音频分离器HHA

HDMI音频分离器HHA简介 HDMI音频分离器HHA具有一路HDMI信号输入&#xff0c;转换成一路HDMI信号、一路5.1光纤音频信号、一路5.1 SPDIF/同轴音频信号和一路模拟左右声道立体声信号输出&#xff0c;同时还支持EDID存储及兼容HDCP功能&#xff1b;分辨率最高支持1920*1080p&#…...

天猫数据分析:2023年坚果炒货市场年销额超71亿,混合坚果成多数消费者首选

近年来&#xff0c;随着人们生活水平和健康意识的提升&#xff0c;在休闲零食市场中&#xff0c;消费者们也越来越关注食品的营养价值&#xff0c;消费者这一消费偏好的转变也为坚果炒货食品行业带来了发展契机。 整体来看&#xff0c;坚果炒货市场的体量较大。根据鲸参谋电商…...

YouTrack 用户登录提示 JIRA 错误

就算输入正确的用户名和密码&#xff0c;我们也得到了下面的错误信息&#xff1a; youtrack Cannot retrieve JIRA user profile details. 解决办法 出现这个问题是因为 YouTrack 在当前的系统重有 JIRA 的导入关联。 需要把这个导入关联取消掉。 找到后台配置的导入关联&a…...

题目 1163: 排队买票

题目描述: 有M个小孩到公园玩&#xff0c;门票是1元。其中N个小孩带的钱为1元&#xff0c;K个小孩带的钱为2元。售票员没有零钱&#xff0c;问这些小孩共有多少种排队方法&#xff0c;使得售票员总能找得开零钱。注意&#xff1a;两个拿一元零钱的小孩&#xff0c;他们的位置互…...

【lesson9】高并发内存池Page Cache层释放内存的实现

文章目录 Page Cache层释放内存的流程Page Cache层释放内存的实现 Page Cache层释放内存的流程 如果central cache释放回一个span&#xff0c;则依次寻找span的前后page id的没有在使用的空闲span&#xff0c;看是否可以合并&#xff0c;如果合并继续向前寻找。这样就可以将切…...

Java基础面试题-6day

I/O流基础知识总结 &#xff08;1&#xff09; io即输入输出流&#xff0c; 如何区分输入还是输入流 以内存为中介&#xff0c;当我们是将数据存储到内存即为输入&#xff0c;反之存储到外部存储器&#xff0c;即为输出 在Java中分输入输出流&#xff0c;根据数据处理又可以分…...

【Oracle 集群】RAC知识图文详细教程(三)--RAC工作原理和相关组件

RAC 工作原理和相关组件 OracleRAC 是多个单实例在配置意义上的扩展&#xff0c;实现由两个或者多个节点&#xff08;实例&#xff09;使用一个共同的共享数据库&#xff08;例如&#xff0c;一个数据库同时安装多个实例并打开&#xff09;。在这种情况下&#xff0c;每一个单独…...

二级C语言笔试2

(总分100,考试时间90分钟) 一、选择题 下列各题A)、B)、C)、D)四个选项中&#xff0c;只有一个选项是正确的。 1. 下列叙述中正确的是( )。 A) 算法的效率只与问题的规模有关&#xff0c;而与数据的存储结构无关 B) 算法的时间复杂度是指执行算法所需要的计算工作量 …...

如何计算两个指定日期相差几年几月几日

一、题目要求 假定给出两个日期&#xff0c;让你计算两个日期之间相差多少年&#xff0c;多少月&#xff0c;多少天&#xff0c;应该如何操作呢&#xff1f; 本文提供网页、ChatGPT法、VBA法和Python法等四种不同的解法。 二、解决办法 1. 网页计算法 这种方法是利用网站给…...

再识C语言 DAY13 【递归函数(超详细)】

文章目录 前言一、函数递归什么是递归递归的两个重要条件练习一练习二 递归与迭代练习三练习四在练习三、四中出现的问题 如果您发现文章有错误请与我留言&#xff0c;感谢 前言 本文总结于此文章 一、函数递归 什么是递归 函数调用自身的编程技巧称为递归 &#xff08;函数自…...

AudioTrack的理解

采样率说的是一秒钟采样多少点 波形频率说的是一个采样周期内有多少个波形 pcm编码说的是 16 还是8 直接决定write的时候使用short还是byte ‌一、初始化配置 ‌参数设定‌ 需定义音频格式、采样率及缓冲区大小&#xff0c;确保符合硬件支持范围 // 音频参数配置 int sample…...

Linux 中 m、mm、mmm 函数和 make 的区别

在 Linux 内核开发和 Android 开发中&#xff0c;构建系统通常使用 make 命令来编译和构建项目。而在 Android 开发环境中&#xff0c;还有 m、mm 和 mmm 等命令&#xff0c;这些命令是 Android 构建系统的一部分&#xff0c;提供了更高效和便捷的构建方式。以下将详细介绍这些…...

stm与51单片机哪个更适合新手学

一句话总结 51单片机&#xff1a;像学骑自行车&#xff0c;简单便宜&#xff0c;但只能在小路上骑。 STM32&#xff1a;像学开汽车&#xff0c;复杂但功能强&#xff0c;能上高速公路&#xff0c;还能拉货载人&#xff08;做复杂项目&#xff09;。 1. 为啥有人说“先学51单片…...

android平台驱动开发(六)--Makefile和Kconfig简介

Makefile&#xff1a; 1.编译进内核&#xff0c;还是以模块方式加载 模块方式编译成ko,通常是自己添加脚本方式insmod ,android 平台通常默认有modprobe加载&#xff0c;不需要额外添加insmod脚本 lsmod |grep test 可以查看是否安装成功 rmmod test-m.ko 可以删除ko 2.多…...

论文阅读(六)Open Set Video HOI detection from Action-centric Chain-of-Look Prompting

论文来源&#xff1a;ICCV&#xff08;2023&#xff09; 项目地址&#xff1a;https://github.com/southnx/ACoLP 1.研究背景与问题 开放集场景下的泛化性&#xff1a;传统 HOI 检测假设训练集包含所有测试类别&#xff0c;但现实中存在大量未见过的 HOI 类别&#xff08;如…...

IP 风险画像技术略解

IP 风险画像的技术定义与价值 IP 风险画像通过整合 IP 查询数据与 IP 离线库信息&#xff0c;结合机器学习算法&#xff0c;为每个 IP 地址生成多维度风险评估模型。其核心价值在于将传统的静态 IP 黑名单升级为动态风险评估体系&#xff0c;可实时识别新型网络威胁&#xff0…...

Hadoop MapReduce:大数据处理利器

Hadoop 的 MapReduce 是一种用于处理大规模数据集的分布式计算框架&#xff0c;基于“分而治之”思想设计。以下从核心概念、工作流程、代码结构、优缺点和应用场景等方面详细讲解&#xff1a; ​​一、MapReduce 核心概念​​ ​​核心思想​​&#xff1a; ​​Map&#xff0…...

GNSS终端授时之四:高精度的PTP授时

我们在GNSS终端的授时之三&#xff1a;NTP网络授时中介绍了NTP网络授时的基本原理。我们知道了NTP授时的精度跟网络环境相关&#xff0c;即使在局域网中NTP授时的精度也只能到ms级别。如果广域网&#xff0c;经过多级交换机&#xff0c;路由器&#xff0c;由于传输路径和延时的…...

stm32cube ide如何将工具链替换成arm-none-eabi-gcc

在 STM32Cube IDE 中替换工具链为GNU Arm Embedded Toolchain (arm-none-eabi-gcc)&#xff0c;可按以下步骤操作&#xff1a; 1. 检查是否已安装工具链 首先确认系统中是否已安装 arm-none-eabi-gcc&#xff1a; Windows&#xff1a;检查环境变量 PATH 中是否包含工具链路径…...

医疗数理范式化:从范式迁移到认知革命的深度解析

引言 在当代医疗领域,数理思维已经从辅助工具逐渐发展成为核心决策支持系统的关键组成部分。随着数字技术的迅猛发展,医疗行业正经历着前所未有的变革,而数理思维作为这一变革的核心驱动力,正在深刻重塑医疗实践的方方面面。数理思维在医疗领域的应用,本质上是将抽象的数…...