稍微解释一下虚拟内存(很明显,不是物理内存),它是计算机系统内存管理的一种技术。 内存映射文件主要的用处是增加 I/O 性能,特别是针对大文件。 需要注意的是,其他映射同一个文件的程序可能不能立即看到这些修改,多个程序同时进行文件映射的行为依赖于操作系统。 2)虚拟内存由操作系统来决定什么时候刷新到磁盘,这个时间不太容易被程序控制。 3)MappedByteBuffer 的回收方式比较诡异。 由此得出的结论就是:内存映射文件,上G大文件轻松处理。 05、最后 本篇文章主要介绍了 Java 的内存映射文件,MappedByteBuffer 是其灵魂,读取速度快如火箭。
在 Linux操作系统中非常重要,因为其涉及到高效的跨进程通信 & 文件操作 今天,我将详细讲解操作系统中核心概念:内存映射 ---- 目录 ? 实现过程 内存映射的实现过程主要是通过Linux系统下的系统调用函数:mmap() 该函数的作用 = 创建虚拟内存区域 + 与共享对象建立映射关系 其函数原型、具体使用 & 内部流程 如下 /** 应用场景 在Linux系统下,根据内存映射的本质原理 & 特点,其应用场景在于: 实现内存共享:如 跨进程通信 提高数据读 / 写效率 :如 文件读 / 写操作 ---- 6. 实例讲解 下面,我将详细讲解 内存映射应用在跨进程通信 & 文件操作的实例 6.1 文件读 / 写操作 传统的Linux系统文件操作流程如下 ? 使用了内存映射的 文件读 / 写 操作 ? 总结 本文全面讲解了 Linux操作系统中的内存映射 下面我将继续讲解 编程开发的基础知识,有兴趣可以继续关注Carson_Ho的开发笔记 ---- 请点赞 / 评论点赞!
领8888元新春采购礼包,抢爆款2核2G云服务器95元/年起,个人开发者加享折上折
前面文章介绍了进程间常用的通信方式: 无名管道和命名管道,这篇文章介绍内存映射,内存映射在多进程访问文件读写的时候非常方便。 1. 内存映射mmap函数介绍 mmap函数可以将磁盘上的文件映射到内存空间中,返回映射的首地址。 返回值:成功返回映射的内存的起始地址。 (1) 第一个参数start指向欲对应的内存起始地址,通常设为NULL,代表让系统自动选定地址,对应成功后该地址会返回。 ,用来取消参数start所指的映射内存起始地址,参数length则是欲取消的内存大小。 案例代码: 多进程并发拷贝一个大文件 代码要求: 使用mmap函数映射文件到内存。
第五个参数是文件描述符fd,要映射的文件对应的文件描述符fd。使用open系统调用获取文件描述符。 第六个参数off_t offset映射文件指针的偏移量,偏移量必须是4KB的整数倍,一般传入0即可,除非有特殊的需求。 返回值: 成功时,返回内存映射区的首地址。 功能: 将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。 (1)使用普通文件提供的内存映射: 适用于任何进程之间。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。 fcntl.h> int main(){ //打开一个文件 int fd = open("a.txt",O_RDWR); int len = lseek(fd,0,SEEK_END); //创建内存映射区
内存映射文件 内存映射文件是由一个文件到一块内存的映射。 应用程序可以通过内存指针对磁盘上的文件进行访问,就如同访问加载了文件的内存,因此内存文件映射非常适合于用来管理大文件。 内存映射文件架构在程序的地址空间之上 32位机地址空间只有4G,而某些大文件的尺寸可要要远超出这个值,因此,用地址空间中的某段应用文件中的一部分可解决处理大文件的问题,在32中,使用内存映射文件可以处理 2的64次(64EB)大小的文件.原因内存映射文件,除了处理大文件,还可用作进程间通信。 ,这个过程有系统调用mmap()实现,所以映射的效率很高。 内存映射文件的效率 了解过内存映射文件都知道,它比传统的IO读写数据快很多,那么,它为什么会这么快,从代码层面上来看,从硬盘上将文件读入内存,都是要经过数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的
查看DirectByteBuffer源码部分unsafe.allocateMemory(size); unSafe 表示native内存,native内存表示操作系统内存(文件读写流程需要经过应用内存- 内存映射文件在windows 系统与linux系统中都有使用,与虚拟内存有些类似,虚拟内存是指当主存(内存)容量不够使用一部分外存(磁盘)充当主存,内存映射文件使用内存虚拟空间地址与磁盘文件建立一种映射关系 ->磁盘文件,内存映射文件持有磁盘地址,在访问时通过地址映射转换直接访问磁盘空间,不需要经过内核空间到用户空间的传输,需要理解的内存映射文件对于应用程序或者操作系统都是透明的,二者均可访问。 应用场景包括:大文件之间的传输、zero copy. 大文件传输: 按照常理文件传输流程: 磁盘-> 内核空间->用户空间->内核空间->磁盘,中间进行多次数据的拷贝,使用内存文件映射方式传输,两个进程都可访问内存映射文件,使得在文件传输变为内存映射文件的传输
我们可以直接把这段空间映射到进程的内存中,就像这样: 假设文件长度是100字节,我们把该文件映射到了进程的内存中,地址是从600 ~ 800,那么当你直接读写600 ~ 800这段内存时,实际上就是在直接操作磁盘文件 这个优势在于处理大文件场景,这里的大文件指的是文件的大小超过你的物理内存,在这种场景下如果你使用传统的read/write,那么你必须一块一块的把文件搬到内存,处理完文件的一小部分再处理下一部分。 但如果用mmap情况就不一样了,只要你的进程地址空间足够大,可以直接把这个大文件映射到你的进程地址空间中,即使该文件大小超过物理内存也可以,这就是虚拟内存的巧妙之处了,当物理内存的空闲空间所剩无几时虚拟内存会把你进程地址空间中不常用的部分扔出去 ,这样你就可以继续在有限的物理内存中处理超大文件了,这个过程对程序员是透明的,虚拟内存都给你处理好了。 使用mmap处理大文件要注意一点,如果你的系统是32位的话,进程的地址空间就只有4G,这其中还有一部分预留给操作系统,因此在32位系统下可能不足以在你的进程地址空间中找到一块连续的空间来映射该文件,在64
零拷贝给我们带来的好处 减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务 减少内存带宽的占用 通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换 零拷贝的实现 零拷贝完全依赖于操作系统。操作系统支持,就有;不支持,就没有。不依赖Java本身。 广义的实现流程参考图(来源网络): ? java具体如何实现,操作内核态(pageCache)的数据呢: 1.通过MMAP实现,将会经历,3次拷贝: 1次cpu copy,2次DMA copy,以及4次上下文切换(中间,在应用中-用户空间可以操作映射的数据 高并发场景下,为了防止 PageCache 被大文件占满后不再对小文件产生作用,大文件不应使用 PageCache,进而也不应使用零拷贝技术处理。 高并发场景处理大文件时,应当使用异步 IO 和直接 IO 来替换零拷贝技术
与多线程相比,IO多路复用技术降低系统开销,不需要创建新的额外进程或者线程,节省了系统资源。 目前支持IO多路复用的系统调用有select、pselect、poll和epoll。 epoll相对于select的改进: 1.一个进程打开的socket描述符(fd)不受限制(受限于操作系统最大文件句柄数)。 epoll没有这个限制,支持的最大fd上限是操作系统最大文件句柄数。1G内存的机器大约是10万个句柄。 3.使用mmap加速内核与用户空间的消息传递 epoll通过内核和用户空间mmap同一块内存来实现消息传递。 mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。
Linux系统内存管理知识补充 Linux系统是虚拟内存系统,虚拟内存并不是真正的物理内存,而是虚拟的连续内存地址空间。 内核会为每个进程分配独立的连续的虚拟内存空间,并且在需要的时候映射物理内存,为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系,这个页表就是存在于MMU中;用户进程访问内存的时候 虚拟内存和物理内存的映射都是等空间的,映射的物理内存是多大的,那么占用的虚拟内存差不多也是多大,都是4KB的整数倍。比如映射了一个1KB的内存空间,那么也是占用一页4KB虚拟内存。 ** # 通过mmap实现的零拷贝(常用来处理大文件) 当进行mmap系统调用的时候,将文件的内容的全部或一部分直接映射到进程的地址空间(虚拟内存),映射完成后,进程可以像访问普通内存一样做其他的操作, mmap系统指令;在读取大文件的时候用这种方法映射大文件的一部分到内存空间,比较方便快捷。
data.resize(10); memset(data.data(),0x00,data.size()); file.write(data); file.close(); 1.4 通过内存映射读写文件 uchar *QFileDevice::map(qint64 offset, qint64 size, MemoryMapFlags flags = NoOptions) 从offset开始将文件大小字节映射到内存中 为了使映射成功,应该打开一个文件,但在映射内存之后,文件不需要保持打开状态。当QFile被销毁或使用此对象打开一个新文件时,任何未被取消映射的映射将自动取消映射。 映射将具有与文件相同的打开模式(读和/或写),除非使用MapPrivateOption,在这种情况下,总是可以写入映射的内存。 返回一个指向内存的指针,如果有错误则返回0。 大文件的读写 在进行大文件读写是采用分段读写的思想,将大文件所有拆成一块一块的读写。
)直接访问系统主内存的机制。 零拷贝实现方式 在Linux中零拷贝的实现方式主要有: mmap + write、sendfile、splice mmap+write(内存映射) mmap 是 Linux 提供的一种内存映射文件方法, 即将一个进程的地址空间中的一段虚拟地址映射到磁盘文件地址。 对于小文件,内存映射文件反而会导致碎片空间的浪费,因为内存映射总是要对齐页边界,最小单位是 4 KB,一个 5 KB 的文件将会映射占用 8 KB 内存,也就会浪费 3 KB 内存。 这是因为在大文件传输场景下,每当用户访问这些大文件的时候,内核就会把它们载入 PageCache 中,PageCache 空间很快被这些大文件占满;且由于文件太大,可能某些部分的文件数据被再次访问的概率比较低
这属于资源的竞争访问管理需求; 高效的翻译转换管理需求:需要实现快速高效的映射翻译转换,否则系统的运行效率将会低下; 高效的虚实内存交换需求:需要在实际的虚拟内存与物理内存进行内存页/段交换过程中快速高效 虚拟内存为每个进程提供了一个一致的、私有且连续完整的内存空间;所有现代操作系统都使用虚拟内存,使用虚拟地址取代物理地址,主要有以下几点好处: 利用上述的第一条特性可以优化,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址 COW 是一种建立在虚拟内存重映射技术之上的技术,因此它需要 MMU 的硬件支持,MMU 会记录当前哪些内存页被标记成只读,当有进程尝试往这些内存页中写数据的时候,MMU 就会抛一个异常给操作系统内核, 此外,在实际应用的过程中,为了避免频繁的内存映射,可以重复使用同一段内存缓冲区,因此,你不需要在只用过一次共享缓冲区之后就解除掉内存页的映射关系,而是重复循环使用,从而提升性能。 但这种内存页映射的持久化并不会减少由于页表往返移动/换页和 TLB flush所带来的系统开销,因为每次接收到 COW 事件之后对内存页而进行加锁或者解锁的时候,内存页的只读标志 (read-ony)
这属于资源的竞争访问管理需求高效的翻译转换管理需求:需要实现快速高效的映射翻译转换,否则系统的运行效率将会低下高效的虚实内存交换需求:需要在实际的虚拟内存与物理内存进行内存页/段交换过程中快速高效Page ,减少出错 利用上述的第一条特性可以优化,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样在 I/O 操作时就不需要来回复制了 图片NFS文件系统网络文件系统是FreeBSD支持的文件系统中的一种 ,为了避免频繁的内存映射,可以重复使用同一段内存缓冲区,因此,你不需要在只用过一次共享缓冲区之后就解除掉内存页的映射关系,而是重复循环使用,从而提升性能但这种内存页映射的持久化并不会减少由于页表往返移动 I/O 模式,Linux 必须要在每一个 I/O 操作时都进行内存虚拟映射和解除。 fbufs 只需通过一次虚拟内存映射操作即可创建缓冲区,有效地消除那些由存储一致性维护所引发的大多数性能损耗共享缓冲区技术的实现需要依赖于用户进程、操作系统内核、以及 I/O 子系统 (设备驱动程序,文件系统等
模式一:Hash映射+Hash统计+堆/归并排序 一、解决思路 1. hash映射(分而治之) 首先考虑是否需要将大文件分成小文件,针对数据太大,内存受限,只能是将大文件化成小文件(取模映射); 2. hash统计 当大文件转化了小文件,那么我们便可以采用常规的Hashmap(ip,value)来进行频率统计; 3. (1)由于IP是32位的,最多有个2^32个IP,约4GB; (2)可以采用映射的方法,比如模1000,把整个日志大文件映射为1000个小文件; (3)再找出每个小文中出现频率最大的IP(可以采用Hash_map 因此我们可以考虑把他们都放进内存中去,而现在只是需要一个合适的数据结构,在这里,Hash Table绝对是我们优先的选择。所以我们摒弃分而治之/hash映射的方法,直接上hash统计,然后排序。 所以不可能将其完全加载到内存中处理。考虑采取分而治之的方法。
什么是内存映射文件 内存映射文件(memory-mapped file)是将完整或者部分文件加载到内存中,这样就可以通过内存地址相关的load或者store指令来操纵文件。 为了支持这个功能,现代的操作系统会提供一个叫做mmap的系统调用。 由于虚拟内存代表的附加抽象层,我们可以映射比机器的物理内存容量大得多的文件。正在运行的进程所需的内存段(称为页)从外部存储中获取,并由虚拟内存管理器自动复制到主内存中。 使用内存映射文件可以提高I/O性能,因为通过系统调用进行的普通读/写操作比在本地内存中进行更改要慢得多,对于操作系统来说,文件以一种“惰性”的方式加载,通常一次只加载一个页,因此即使对于较大的文件,实际 RAM利用率也是最低的,但是使用内存映射文件可以改善这个流程。
“大文件分割器”到来了,用它可以轻松分割数G的大文件: ? 用大文件分割器分割以后,就再也不存在这个问题: ? ? 界面是MFC写的,关于如何处理GB级别文件,使用的是windows API,内存映射。 NULL,PAGE_READWRITE,0,dwBlockBytes,NULL); if(hNewFile == NULL) { strFormat.Format("获取分块文件内存映射对象句柄失败 ; __EndPos: //后续处理 return ; } 关于内存映射技术,在此有详细说明:http://www.oschina.net/question/54100_26210 但程序有点问题在于 我电脑内存是4G,或许也跟硬盘有关? 于是我又做了一个64位版本,问题得到了解决,没有再出现内存不足的问题。所以大家如果分割超大文件出错的话可以试试x64版本的,我都放在压缩包里了。
这属于资源的竞争访问管理需求; 高效的翻译转换管理需求:需要实现快速高效的映射翻译转换,否则系统的运行效率将会低下; 高效的虚实内存交换需求:需要在实际的虚拟内存与物理内存进行内存页/段交换过程中快速高效 虚拟内存为每个进程提供了一个一致的、私有且连续完整的内存空间;所有现代操作系统都使用虚拟内存,使用虚拟地址取代物理地址,主要有以下几点好处: 第一点,利用上述的第一条特性可以优化,可以把内核空间和用户空间的虚拟地址映射到同一个物理地址 此外,在实际应用的过程中,为了避免频繁的内存映射,可以重复使用同一段内存缓冲区,因此,你不需要在只用过一次共享缓冲区之后就解除掉内存页的映射关系,而是重复循环使用,从而提升性能。 但这种内存页映射的持久化并不会减少由于页表往返移动/换页和TLB flush所带来的系统开销,因为每次接收到COW事件之后对内存页而进行加锁或者解锁的时候,内存页的只读标志 (read-ony) 都要被更改为 而使用write()系统调用时,则是把用户内存缓冲区的数据拷贝至内核缓冲区。 为了实现这种传统的I/O模式,Linux必须要在每一个I/O操作时都进行内存虚拟映射和解除。
问:使用 Java 如何读取大文件,你有什么建议或者经验? 答:我们平常读取一般文件都是将文件数据直接全部读取到内存中进行操作的,这种做法对于小文件是没有问题的,但对于稍大一些的文件就会抛出 OOM 异常,所以我们应该把大文件分成多个子区域分多次读取。 String(array)); } } while (bytes > 0); byteBuf.clear(); fileChannel.close(); fileIn.close(); 思路三:内存文件映射 ,就是把文件内容映射到虚拟内存的一块区域中,从而可以直接操作内存当中的数据而无需每次都通过 I/O 去物理硬盘读取文件,这种方式可以提高速度,具体样板代码如下。 fileIn.close(); 思路四:使用 RandomAccessFile 的 seek() 方法进行分块读写操作,具体实现非常简单,和普通文件操作一样,不过还是推荐 JDK 1.4 NIO 的内存映射文件
云端获取和启用云服务器,并实时扩展或缩减云计算资源。云服务器 支持按实际使用的资源计费,可以为您节约计算成本。 腾讯云服务器(CVM)为您提供安全可靠的弹性云计算服务。只需几分钟,您就可以在云端获取和启用云服务器,并实时扩展或缩减云计算资源。云服务器 支持按实际使用的资源计费,可以为您节约计算成本。
扫码关注腾讯云开发者
领取腾讯云代金券