正常情况下,JVM创建一个缓冲区的时候,实际上做了如下几件事:
- JVM确保Heap区域内的空间足够,如果不够则使用触发GC在内的方法获得空间;
- 获得空间之后会找一组堆内的连续地址分配数组, 这里需要注意的是,在物理内存上,这些字节是不一定连续的;
对于不涉及到IO的操作,这样的处理没有任何问题,但是当进行IO操作的时候就会出现一点性能问题.
所有的IO操作都需要操作系统进入内核态才行,而JVM进程属于用户态进程, 当JVM需要把一个缓冲区写到某个Channel或Socket的时候,需要切换到内核态.
而内核态由于并不知道JVM里面这个缓冲区存储在物理内存的什么地址,并且这些物理地址并不一定是连续的(或者说不一定是IO操作需要的块结构),所以在切换之前JVM需要把缓冲区复制到物理内存一块连续的内存上, 然后由内核去读取这块物理内存,整合成连续的、分块的内存.
为了解决这个问题, Java的某些版本会把物理区域分配好的部分内存做缓存就不用每次都开辟一块空间,但效果还不够好,毕竟复制的部分是少不了的.
JDK1.4之后引入了NIO, 提供了一种内存映射技术, 让我们可以直接从Java代码中创建DirectBuffer,这种Buffer在创建的时候直接就在物理内存中分配一块连续内存,当需要使用的时候不再需要复制,内核直接调用即可. 但缺点也是显而易见的,就是每次分配都比较昂贵一点,同时由于分配的内存不在Java Heap中,所以也不会受用户设置的堆大小的限制.
通常情况下,大量使用IO操作的时候使用内存映射是非常值得的,下面是我画的一张图,方便记忆: