一鸭丛、傳統(tǒng)IO模式下的文件讀取
傳統(tǒng)的文件IO操作都是調(diào)用OS提供的底層標(biāo)準(zhǔn)IO操作讀取函數(shù)read()方淤、write();然后調(diào)用此函數(shù)的進(jìn)程(即java進(jìn)程)由java用戶態(tài)切換至內(nèi)核態(tài)。然后OS內(nèi)核代碼負(fù)責(zé)將相應(yīng)的文件讀取到內(nèi)核IO緩存之中源请,然后再把數(shù)據(jù)從內(nèi)核IO緩存之中拷貝到進(jìn)程相應(yīng)的私有地址空間中富稻。則完成一次IO操作筒狠;
Q&A:
Q:為什么要搞一個(gè)內(nèi)核IO緩存舔庶,將原本拷貝一次數(shù)據(jù)的操作整兩次抛蚁?
A:因?yàn)闉榱藴p少磁盤的IO操作,提升性能惕橙;因?yàn)槲覀兊某绦蚓哂芯植啃郧扑Γ此^的局部性原理,在這里是空間局部性吕漂;即我們?cè)L問文件中的一段數(shù)據(jù)亲配,接下來可能還會(huì)訪問接下去的一段數(shù)據(jù)尘应,而磁盤的IO操作相對(duì)于內(nèi)存慢了好幾個(gè)數(shù)量級(jí)惶凝,所以O(shè)S根據(jù)局部性原理會(huì)在read()時(shí)吼虎,會(huì)預(yù)讀更多的文件數(shù)據(jù)存放在內(nèi)核IO緩存中,當(dāng)訪問的文件數(shù)據(jù)在內(nèi)核IO緩沖區(qū)之中時(shí)直接將數(shù)據(jù)拷貝到進(jìn)程私有內(nèi)存地址之中(也有可能經(jīng)過了native堆中轉(zhuǎn)苍鲜,因?yàn)檫@些函數(shù)都是聲明為native本地平臺(tái)相關(guān))思灰。避免低效率的磁盤IO讀取。
Q:既然有內(nèi)核IO緩存混滔,那java為什么還提供BufferedInputStream對(duì)象洒疚?
A:因?yàn)閺膬?nèi)核IO緩存中拷貝到進(jìn)程私有空間數(shù)據(jù)系統(tǒng)調(diào)用,而系統(tǒng)調(diào)用相對(duì)來數(shù)代價(jià)是比較高的坯屿,需要從java用戶態(tài)和內(nèi)核態(tài)的上下文切換油湖。
二,java NIO讀取模式
- java內(nèi)存映射文件讀取模式
簡介:將進(jìn)程的用戶私有空間的一部分區(qū)域與文件對(duì)象建立映射關(guān)系领跛。并不需要將文件拷貝到內(nèi)核IO緩存之中乏德,類似于直接從內(nèi)存中讀取數(shù)據(jù),這樣速度當(dāng)然快吠昭。
java之中針對(duì)于內(nèi)存映射提供了三種模式:只讀(readonly)喊括、讀寫(read_write)、專有(private)矢棚;
readonly:異常:針對(duì)只讀模式郑什,如果嘗試寫操作,則ReadonlyBufferException
read_write:可以進(jìn)行讀寫操作蒲肋,表明在通過內(nèi)存文件映射的方式寫或者修改能直接反應(yīng)到文件對(duì)象中去蘑拯; 如果其他進(jìn)程共享了文件對(duì)象,那個(gè)能直接看到變化兜粘;不像標(biāo)準(zhǔn)IO模式强胰,每個(gè)進(jìn)程都有自己的內(nèi)核緩沖;類似于java進(jìn)程妹沙,必須要flash()或者close()操作才能將修改更正到磁盤文件對(duì)象中去偶洋;
write:采用OS“寫時(shí)拷貝”原則,在沒有進(jìn)程寫操作的時(shí)候距糖,多個(gè)進(jìn)程之間都是共享文件的同一快物理內(nèi)存(即各個(gè)進(jìn)程的虛擬地址指向同一片物理地址)玄窝;一旦某個(gè)進(jìn)程進(jìn)行寫操作,則會(huì)將受影響的文件數(shù)據(jù)單獨(dú)拷貝一份到進(jìn)程的私有緩沖區(qū)中去悍引,而不會(huì)反映到物理文件中去恩脂;
備注:
-
java中對(duì)于進(jìn)程單次文件IO限制Integer.MAX_VALUE,即2G左右趣斤,但是可以通過分次映射文件不同部分來達(dá)到操作整個(gè)文件的目的俩块。
- java內(nèi)存映射文件數(shù)據(jù)JVM直接緩沖區(qū), 還可以通過 ByteBuffer.allocateDirect() ,即DirectMemory的方式來創(chuàng)建直接緩沖區(qū)玉凯。他們相比基礎(chǔ)的 IO操作來說就是少了中間緩沖區(qū)的數(shù)據(jù)拷貝開銷势腮。同時(shí)他們屬于JVM堆外內(nèi)存,不受JVM堆內(nèi)存大小的限制漫仆。
package com.seriousty.practice.memory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
/**
* Created with IntelliJ IDEA
* Created By seriousty
* Date: 2018/8/24
* Time: 15:05
* BOLG: [https://github.com/seriousty](https://github.com/seriousty)
* Description:
*/
public class IOTest {
private static int MAX_SIZE = 1024;
/**
*NIO操作:
*/
public static void main(String[] args) {
File file=new File("[h://iofile.pdf](file:///h://iofile.pdf)");
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
FileChannel channel = fis.getChannel();
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());//time consuming:39
byte[] bytes=new byte[1024];
int length = (int) file.length();
long start = System.currentTimeMillis();
for (int i = 0; i <length ; i+=1024) {
if(length-i>MAX_SIZE){
map.get(bytes);
}else{
map.get(new byte[length-i]);
}
}
long end = System.currentTimeMillis();
System.out.println("time consuming:"+(end-start));//time consuming:41
} catch (Exception e) {
e.printStackTrace();
}
}
}
普通IO模式:
public static void main(String[] args) {
File file = new File("[h://iofile.pdf](file:///h://iofile.pdf)");
try {
FileInputStream fis = new FileInputStream(file);
FileChannel channel = fis.getChannel();
ByteBuffer allocate = ByteBuffer.allocate(1024);
//ByteBuffer allocate = ByteBuffer.allocateDirect((int) channel.size());
long start = System.currentTimeMillis();
while (channel.read(allocate) != -1) {
allocate.flip();
allocate.clear();
}
long end = System.currentTimeMillis();
System.out.println("time consuming:"+(end-start));//time consuming:1014
} catch (Exception e) {
e.printStackTrace();
}
}
2,直接內(nèi)存(Directed Memory)
直接內(nèi)存大小默認(rèn)等同于JVM堆大小 -Xmx:JVM堆大猩诱;
但是直接內(nèi)存大小不受JVM堆的限制 由JVM參數(shù) -XX:MaxDirectMemorySize 單獨(dú)配置
給直接內(nèi)存設(shè)置最大內(nèi)存大小
給直接內(nèi)存分配內(nèi)存
結(jié)論:
執(zhí)行了多次Full GC,沒有執(zhí)行GC(不受新生代的影響)盲厌,只有當(dāng)回收老年代的時(shí)候再回順便回收直接內(nèi)存署照?why,因?yàn)橹苯觾?nèi)存是通過DirectByteBuffer對(duì)象來引用的吗浩,所以當(dāng)DirectByteBuffer對(duì)象由新生代進(jìn)入老年代后建芙,在老年代觸發(fā)了Full GC.
總結(jié):
NIO中的DirectMemory和內(nèi)存映射文件都是直接內(nèi)存緩沖,但是DirectMemory能通過JVM -XX:+Xmx和-XX:MaxDirectMemorySize參數(shù)來控制懂扼,內(nèi)存映射文件沒有JVM參數(shù)可以控制岁钓;
兩者內(nèi)存分配位置:
DirectMemory:在java進(jìn)程中的native堆中分配,不受young gc控制微王,避免了在java堆和native堆中copy屡限,提高文件傳輸?shù)男阅?/p>
放java向外進(jìn)行數(shù)據(jù)傳輸時(shí),需要先將數(shù)據(jù)從java堆拷貝到native堆之中炕倘。
內(nèi)存映射文件:沒有經(jīng)過native堆钧大,由java進(jìn)程私有內(nèi)存空間一部分于文件對(duì)象建立關(guān)聯(lián)的映射關(guān)系。所以也不會(huì)受young gc影響罩旋。
資料引用:
1) Heap memory: memory within the JVM process that is managed by the JVM to represent Java objects
2) Native memory/Off-heap: is memory allocated within the processes address space that is not within the heap.
3) Direct memory: is similar to native, but also implies that an underlying buffer within the hardware is being shared. For example buffer within the network adapter or graphics display. The goal here is to reduce the number of times the same bytes is being copied about in memory.