系列文章目录
学懂IO必备的操作系统知识(一)
学懂IO必备的操作系统知识(二)
学懂IO必备的TCP、socket知识(三)
该篇文章是依赖于第一篇,直接看可能会有很多疑问,请先查看第一篇。
1、pagecache
上一篇提到了pagecache,这里再补充下。
- 在计算机中应用第一次加载某个文件,会产生缺页,这个时候需要kernel从磁盘加载数据到pagecache中、
- 多个应用操作同一个文件数据只会在在pagecach加载一次
- 每个应用通过fd找到自己的文件指针操作文件
- 内存管理单元维护线性内存和物理内存的关系
- 线性内存是逻辑上连续的, 物理内存是不连续的会产生碎片
- pagecache由内核维护,大小有物理内存决定
- 当数据page页新建或者修改后没有刷盘,称为脏页,此时直接断电了,会发生丢数据情况,而是根据配置的百分比阈值、时间阈值触发刷盘。
- pagecache觉有内存淘汰机制;
- 文件不一定是全量加载到pagecache,需要时加载
2、on heap &off heap
on heap &off heap
一般情况下,Java中分配的非空对象都是由Java虚拟机的垃圾收集器管理的,也称为堆内内存(on-heap memory)。
堆外内存是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理而不是虚拟机。
3、ByteBuffer
ByteBuffer
在旧I/O类库中(相对java.nio包)中的BufferedInputStream、BufferedOutputStream、BufferedReader和BufferedWriter在其实现中都运用了缓冲区。java.nio包公开了Buffer API,使得Java程序可以直接控制和运用缓冲区。
在Java NIO中,缓冲区直接为通道(Channel)服务,写入数据到通道或从通道读取数据,都是利用缓冲区来传递数据,达到了对数据的高效处理。在NIO中主要有八种缓冲区类(其中MappedByteBuffer是专门用于内存映射的一种ByteBuffer):
所有缓冲区都有4个属性:capacity、limit、position、mark:
Capacity //容量,即可以容纳的最大数据量;在缓冲区创建时被设定并且不能改变
Limit //表示缓冲区的当前终点,不能对缓冲区超过极限的位置进行读写操作。且极限是可以修改的
Position //位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变改值,为下次读写作准备
Mark //标记,调用mark()来设置mark=position,再调用reset()可以让position恢复到标记的位置
常用到的方法:
limit(), limit(10)//其中读取和设置属性的方法的命名和jQuery中的val(),val(10)类似,一个负责get,一个负责set
clear()//position = 0;limit = capacity;mark = -1; 有点初始化的味道,但是并不影响底层byte数组的内容
reset()//把position设置成mark的值,相当于之前做过一个标记,现在要退回到之前标记的地方
put(byte b) //相对写,向position的位置写入一个byte,并将postion+1,为下次读写作准备
flip()//limit = position;position = 0;mark = -1; 翻转,就是将一个处于存数据状态的缓冲区变为一个处于准备取数据的状态
get()//相对读,从position位置读取一个byte,并将position+1,为下次读写作准备
get(int index) //绝对读,读取byteBuffer底层的bytes中下标为index的byte,不改变position
例子:
@Test
public void whatByteBuffer(){
// ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
System.out.println("postition: " + buffer.position());
System.out.println("limit: " + buffer.limit());
System.out.println("capacity: " + buffer.capacity());
System.out.println("mark: " + buffer);
buffer.put("123".getBytes());
System.out.println("-------------put:123......");
System.out.println("mark: " + buffer);
buffer.flip(); //读写交替
System.out.println("-------------flip......");
System.out.println("mark: " + buffer);
buffer.get();
System.out.println("-------------get......");
System.out.println("mark: " + buffer);
buffer.compact();
System.out.println("-------------compact......");
System.out.println("mark: " + buffer);
buffer.clear();
System.out.println("-------------clear......");
System.out.println("mark: " + buffer);
}
java在对数据进行跟更底层的操作时,经常用到ByteBuffer。它提供了两种分配内存的方式:
//在jvm内部分配的,由jvm管理
public static ByteBuffer allocate(int capacity)
//jvm外,系统级的内存分配,
public static ByteBuffer allocateDirect(int capacity)
当Java程序接收到外部传来的数据时,首先是被系统内存所获取,然后在由系统内存复制复制到JVM内存中供Java程序使用。所以在另外一种分配方式中,能够省去复制这一步操作,效率上会有所提高。可是系统级内存的分配比起JVM内存的分配要耗时得多,所以并非不论什么时候allocateDirect的操作效率都是最高的。
关系图 :
2、通道-channel
每个IO Channel和一个文件或者类似于文件的对象相联系,在Linux上,IO Channel可以和打开文件描述符,如Socket, 管道等连接,如此以后就可以像访问文件一样操作IO Channels。
通道表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区,然后操作缓冲区,对数据进行处理。Channel相比IO中的Stream更加高效,可以异步双向传输,但是必须和buffer一起使用。
大白话: channel就是个普通的类,只不过包装了跟fd的连接而已,作用相当于铁路,可以来回走,bytebuffer 相当于火车装载数据,在上面跑。
后面会有文章专门讲解nio channel … …
问题:
1、使用epoll 的时候,计算机是怎么知道数据到达的?
答:通过dma中断通知的。
2、当程序通过read()、writ()是否通过应用缓存?
答:当用户进程使用read 和 write 读写Linux的文件时,进程会从用户态进入内核态,通过I/O操作读取文件中的数据。read()和write(),这两个系统调用在用户态都是没有缓存。