JAVA NIO之淺談內(nèi)存映射文件原理與DirectMemory(轉(zhuǎn))

轉(zhuǎn)自 [Java][IO]JAVA NIO之淺談內(nèi)存映射文件原理與DirectMemory

Java類庫(kù)中的NIO包相對(duì)于IO 包來(lái)說(shuō)有一個(gè)新功能是內(nèi)存映射文件睹逃,日常編程中并不是經(jīng)常用到背苦,但是在處理大文件時(shí)是比較理想的提高效率的手段呻率。本文我主要想結(jié)合操作系統(tǒng)中(OS)相關(guān)方面的知識(shí)介紹一下原理片酝。

在傳統(tǒng)的文件IO操作中暗甥,我們都是調(diào)用操作系統(tǒng)提供的底層標(biāo)準(zhǔn)IO系統(tǒng)調(diào)用函數(shù) read()框往、write() ,此時(shí)調(diào)用此函數(shù)的進(jìn)程(在JAVA中即java進(jìn)程)由當(dāng)前的用戶態(tài)切換到內(nèi)核態(tài)知允,然后OS的內(nèi)核代碼負(fù)責(zé)將相應(yīng)的文件數(shù)據(jù)讀取到內(nèi)核的IO緩沖區(qū)撒蟀,然 后再把數(shù)據(jù)從內(nèi)核IO緩沖區(qū)拷貝到進(jìn)程的私有地址空間中去,這樣便完成了一次IO操作温鸽。至于為什么要多此一舉搞一個(gè)內(nèi)核IO緩沖區(qū)把原本只需一次拷貝數(shù)據(jù) 的事情搞成需要2次數(shù)據(jù)拷貝呢保屯? 我想學(xué)過(guò)操作系統(tǒng)或者計(jì)算機(jī)系統(tǒng)結(jié)構(gòu)的人都知道,這么做是為了減少磁盤的IO操作涤垫,為了提高性能而考慮的姑尺,因?yàn)槲覀兊某绦蛟L問(wèn)一般都帶有局部性,也就是所 謂的局部性原理蝠猬,在這里主要是指的空間局部性切蟋,即我們?cè)L問(wèn)了文件的某一段數(shù)據(jù),那么接下去很可能還會(huì)訪問(wèn)接下去的一段數(shù)據(jù)吱雏,由于磁盤IO操作的速度比直接 訪問(wèn)內(nèi)存慢了好幾個(gè)數(shù)量級(jí)敦姻,所以O(shè)S根據(jù)局部性原理會(huì)在一次 read()系統(tǒng)調(diào)用過(guò)程中預(yù)讀更多的文件數(shù)據(jù)緩存在內(nèi)核IO緩沖區(qū)中,當(dāng)繼續(xù)訪問(wèn)的文件數(shù)據(jù)在緩沖區(qū)中時(shí)便直接拷貝數(shù)據(jù)到進(jìn)程私有空間歧杏,避免了再次的低 效率磁盤IO操作。在JAVA中當(dāng)我們采用IO包下的文件操作流迷守,如:

FileInputStream in = new FileInputStream("D:\\java.txt");
in.read();

JAVA虛擬機(jī)內(nèi)部便會(huì)調(diào)用OS底層的 read()系統(tǒng)調(diào)用完成操作犬绒,如上所述,在第二次調(diào)用 in.read()的時(shí)候可能就是從內(nèi)核緩沖區(qū)直接返回?cái)?shù)據(jù)了(可能還有經(jīng)過(guò) native堆做一次中轉(zhuǎn)兑凿,因?yàn)檫@些函數(shù)都被聲明為 native凯力,即本地平臺(tái)相關(guān),所以可能在C代碼中有做一次中轉(zhuǎn)礼华,如 win32中是通過(guò) C代碼從OS讀取數(shù)據(jù)咐鹤,然后再傳給JVM內(nèi)存)。既然如此圣絮,JAVA的IO包中為啥還要提供一個(gè) BufferedInputStream 類來(lái)作為緩沖區(qū)呢祈惶。關(guān)鍵在于四個(gè)字,"系統(tǒng)調(diào)用"!當(dāng)讀取OS內(nèi)核緩沖區(qū)數(shù)據(jù)的時(shí)候捧请,便發(fā)起了一次系統(tǒng)調(diào)用操作(通過(guò)native的C函數(shù)調(diào)用)凡涩,而系統(tǒng) 調(diào)用的代價(jià)相對(duì)來(lái)說(shuō)是比較高的,涉及到進(jìn)程用戶態(tài)和內(nèi)核態(tài)的上下文切換等一系列操作疹蛉,所以我們經(jīng)常采用如下的包裝:

FileInputStream in = new FileInputStream("D:\\java.txt"); BufferedInputStream buf_in = new BufferedInputStream(in); buf_in.read();

這樣一來(lái)活箕,我們每一次 buf_in.read() 時(shí)候,BufferedInputStream 會(huì)根據(jù)情況自動(dòng)為我們預(yù)讀更多的字節(jié)數(shù)據(jù)到它自己維護(hù)的一個(gè)內(nèi)部字節(jié)數(shù)組緩沖區(qū)中可款,這樣我們便可以減少系統(tǒng)調(diào)用次數(shù)育韩,從而達(dá)到其緩沖區(qū)的目的。所以要明確 的一點(diǎn)是 BufferedInputStream 的作用不是減少 磁盤IO操作次數(shù)(這個(gè)OS已經(jīng)幫我們做了)闺鲸,而是通過(guò)減少系統(tǒng)調(diào)用次數(shù)來(lái)提高性能的座慰。同理 BufferedOuputStream , BufferedReader/Writer 也是一樣的。在 C語(yǔ)言的函數(shù)庫(kù)中也有類似的實(shí)現(xiàn)翠拣,如 fread()版仔,這個(gè)函數(shù)就是 C語(yǔ)言中的緩沖IO,作用與BufferedInputStream()相同.
這里簡(jiǎn)單的引用下JDK6 中 BufferedInputStream 的源碼驗(yàn)證下:

public  class BufferedInputStream extends FilterInputStream {  
 
   private static int defaultBufferSize = 8192;  
 
   /** 
    * The internal buffer array where the data is stored. When necessary, 
    * it may be replaced by another array of 
    * a different size. 
    */  
   protected volatile byte buf[];  
 /** 
    * The index one greater than the index of the last valid byte in  
    * the buffer.  
    * This value is always 
    * in the range <code>0</code> through <code>buf.length</code>; 
    * elements <code>buf[0]</code>  through <code>buf[count-1] 
    * </code>contain buffered input data obtained 
    * from the underlying  input stream. 
    */  
   protected int count;  
 
   /** 
    * The current position in the buffer. This is the index of the next  
    * character to be read from the <code>buf</code> array.  
    * <p> 
    * This value is always in the range <code>0</code> 
    * through <code>count</code>. If it is less 
    * than <code>count</code>, then  <code>buf[pos]</code> 
    * is the next byte to be supplied as input; 
    * if it is equal to <code>count</code>, then 
    * the  next <code>read</code> or <code>skip</code> 
    * operation will require more bytes to be 
    * read from the contained  input stream. 
    * 
    * @see     java.io.BufferedInputStream#buf 
    */  
   protected int pos;  
 
/* 這里省略去 N 多代碼 ------>>  */  
 
 /** 
    * See 
    * the general contract of the <code>read</code> 
    * method of <code>InputStream</code>. 
    * 
    * @return     the next byte of data, or <code>-1</code> if the end of the 
    *             stream is reached. 
    * @exception  IOException  if this input stream has been closed by 
    *              invoking its {@link #close()} method, 
    *              or an I/O error occurs.  
    * @see        java.io.FilterInputStream#in 
    */  
   public synchronized int read() throws IOException {  
   if (pos >= count) {  
       fill();  
       if (pos >= count)  
       return -1;  
   }  
   return getBufIfOpen()[pos++] & 0xff;  
   }  ```
    

  我們可以看到误墓,BufferedInputStream 內(nèi)部維護(hù)著一個(gè) 字節(jié)數(shù)組 byte[] buf 來(lái)實(shí)現(xiàn)緩沖區(qū)的功能蛮粮,我們調(diào)用的  buf_in.read() 方法在返回?cái)?shù)據(jù)之前有做一個(gè) if 判斷,如果 buf 數(shù)組的當(dāng)前索引不在有效的索引范圍之內(nèi)谜慌,即 if 條件成立然想, buf 字段維護(hù)的緩沖區(qū)已經(jīng)不夠了,這時(shí)候會(huì)調(diào)用 內(nèi)部的 fill() 方法進(jìn)行填充欣范,而fill()會(huì)預(yù)讀更多的數(shù)據(jù)到 buf 數(shù)組緩沖區(qū)中去变泄,然后再返回當(dāng)前字節(jié)數(shù)據(jù),如果 if 條件不成立便直接從 buf緩沖區(qū)數(shù)組返回?cái)?shù)據(jù)了恼琼。其中g(shù)etBufIfOpen()返回的就是 buf字段的引用妨蛹。順便說(shuō)下,源碼中的 buf 字段聲明為  protected volatile byte buf[];  主要是為了通過(guò) volatile 關(guān)鍵字保證 buf數(shù)組在多線程并發(fā)環(huán)境中的內(nèi)存可見(jiàn)性.

  和 [Java ](http://lib.csdn.net/base/java)NIO 的內(nèi)存映射無(wú)關(guān)的部分說(shuō)了這么多篇幅晴竞,主要是為了做個(gè)鋪墊蛙卤,這樣才能建立起一個(gè)知識(shí)體系,以便更好的理解內(nèi)存映射文件的優(yōu)點(diǎn)噩死。

  內(nèi)存映射文件和之前說(shuō)的 標(biāo)準(zhǔn)IO操作最大的不同之處就在于它雖然最終也是要從磁盤讀取數(shù)據(jù)颤难,但是它并不需要將數(shù)據(jù)讀取到OS內(nèi)核緩沖區(qū),而是直接將進(jìn)程的用戶私有地址空間中的一 部分區(qū)域與文件對(duì)象建立起映射關(guān)系已维,就好像直接從內(nèi)存中讀行嗤、寫文件一樣,速度當(dāng)然快了垛耳。為了說(shuō)清楚這個(gè)栅屏,我們以[Linux](http://lib.csdn.net/base/linux)操作系統(tǒng)為例子飘千,看下圖:
  ![](http://upload-images.jianshu.io/upload_images/1986284-757609afe70530fb.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  此圖為 Linux 2.X 中的進(jìn)程虛擬存儲(chǔ)器,即進(jìn)程的虛擬地址空間既琴,如果你的機(jī)子是 32 位占婉,那么就有  2^32 = 4G的虛擬地址空間,我們可以看到圖中有一塊區(qū)域: “Memory mapped region for shared libraries” 甫恩,這段區(qū)域就是在內(nèi)存映射文件的時(shí)候?qū)⒛骋欢蔚奶摂M地址和文件對(duì)象的某一部分建立起映射關(guān)系逆济,此時(shí)并沒(méi)有拷貝數(shù)據(jù)到內(nèi)存中去,而是當(dāng)進(jìn)程代碼第一次引用這 段代碼內(nèi)的虛擬地址時(shí)磺箕,觸發(fā)了缺頁(yè)異常奖慌,這時(shí)候OS根據(jù)映射關(guān)系直接將文件的相關(guān)部分?jǐn)?shù)據(jù)拷貝到進(jìn)程的用戶私有空間中去,當(dāng)有操作第N頁(yè)數(shù)據(jù)的時(shí)候重復(fù)這 樣的OS頁(yè)面調(diào)度程序操作松靡。注意啦简僧,原來(lái)內(nèi)存映射文件的效率比標(biāo)準(zhǔn)IO高的重要原因就是因?yàn)樯倭税褦?shù)據(jù)拷貝到OS內(nèi)核緩沖區(qū)這一步(可能還少了 native堆中轉(zhuǎn)這一步)。

  java中提供了3種內(nèi)存映射模式雕欺,即:只讀(readonly)岛马、讀寫(read_write)、專用(private) 屠列,對(duì)于  只讀模式來(lái)說(shuō)啦逆,如果程序試圖進(jìn)行寫操作,則會(huì)拋出ReadOnlyBufferException異 常笛洛;第二種的讀寫模式表明了通過(guò)內(nèi)存映射文件的方式寫或修改文件內(nèi)容的話是會(huì)立刻反映到磁盤文件中去的夏志,別的進(jìn)程如果共享了同一個(gè)映射文件,那么也會(huì)立即 看到變化苛让!而不是像標(biāo)準(zhǔn)IO那樣每個(gè)進(jìn)程有各自的內(nèi)核緩沖區(qū)沟蔑,比如JAVA代碼中,沒(méi)有執(zhí)行 IO輸出流的 flush() 或者  close() 操作狱杰,那么對(duì)文件的修改不會(huì)更新到磁盤去瘦材,除非進(jìn)程運(yùn)行結(jié)束;最后一種專用模式采用的是OS的“寫時(shí)拷貝”原則浦旱,即在沒(méi)有發(fā)生寫操作的情況下宇色,多個(gè)進(jìn)程之 間都是共享文件的同一塊物理內(nèi)存(進(jìn)程各自的虛擬地址指向同一片物理地址),一旦某個(gè)進(jìn)程進(jìn)行寫操作颁湖,那么將會(huì)把受影響的文件數(shù)據(jù)單獨(dú)拷貝一份到進(jìn)程的私 有緩沖區(qū)中,不會(huì)反映到物理文件中去例隆。

  在JAVA NIO中可以很容易的創(chuàng)建一塊內(nèi)存映射區(qū)域甥捺,代碼如下:  

File file = new File("E:\download\office2007pro.chs.ISO");
FileInputStream in = new FileInputStream(file);
FileChannel channel = in.getChannel();
MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 0,channel.size());


  這里創(chuàng)建了一個(gè)只讀模式的內(nèi)存映射文件區(qū)域,接下來(lái)我就來(lái)[測(cè)試](http://lib.csdn.net/base/softwaretest)下與普通NIO中的通道操作相比性能上的優(yōu)勢(shì)镀层,先看如下代碼:
   

public class IOTest {
static final int BUFFER_SIZE = 1024;

public static void main(String[] args) throws Exception {  

    File file = new File("F:\\aa.pdf");  
    FileInputStream in = new FileInputStream(file);  
    FileChannel channel = in.getChannel();  
    MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 0,  
            channel.size());  

    byte[] b = new byte[1024];  
    int len = (int) file.length();  

    long begin = System.currentTimeMillis();  

    for (int offset = 0; offset < len; offset += 1024) {  

        if (len - offset > BUFFER_SIZE) {  
            buff.get(b);  
        } else {  
            buff.get(new byte[len - offset]);  
        }  
    }  

    long end = System.currentTimeMillis();  
    System.out.println("time is:" + (end - begin));  

}  

} ```

輸出為 63镰禾,即通過(guò)內(nèi)存映射文件的方式讀取 86M多的文件只需要78毫秒皿曲,我現(xiàn)在改為普通NIO的通道操作看下:

File file = new File("F:\\liq.pdf");  
FileInputStream in = new FileInputStream(file);  
FileChannel channel = in.getChannel();  
ByteBuffer buff = ByteBuffer.allocate(1024);   
  
long begin = System.currentTimeMillis();  
while (channel.read(buff) != -1) {  
    buff.flip();  
    buff.clear();  
}  
long end = System.currentTimeMillis();  
System.out.println("time is:" + (end - begin));  ```

輸出為 468毫秒,幾乎是 6 倍的差距吴侦,文件越大屋休,差距便越大。所以內(nèi)存映射文件特別適合于對(duì)大文件的操作备韧,JAVA中的限制是最大不得超過(guò) Integer.MAX_VALUE劫樟,即2G左右,不過(guò)我們可以通過(guò)分次映射文件(channel.map)的不同部分來(lái)達(dá)到操作整個(gè)文件的目的织堂。

按照jdk文檔的官方說(shuō)法叠艳,內(nèi)存映射文件屬于JVM中的直接緩沖區(qū),還可以通過(guò) ByteBuffer.allocateDirect() 易阳,即DirectMemory的方式來(lái)創(chuàng)建直接緩沖區(qū)附较。他們相比基礎(chǔ)的 IO操作來(lái)說(shuō)就是少了中間緩沖區(qū)的數(shù)據(jù)拷貝開(kāi)銷。同時(shí)他們屬于JVM堆外內(nèi)存潦俺,不受JVM堆內(nèi)存大小的限制拒课。
 
其中 DirectMemory 默認(rèn)的大小是等同于JVM最大堆,理論上說(shuō)受限于 進(jìn)程的虛擬地址空間大小事示,比如 32位的windows上早像,每個(gè)進(jìn)程有4G的虛擬空間除去 2G為OS內(nèi)核保留外,再減去 JVM堆的最大值很魂,剩余的才是DirectMemory大小扎酷。通過(guò) 設(shè)置 JVM參數(shù) -Xmx64M,即JVM最大堆為64M遏匆,然后執(zhí)行以下程序可以證明DirectMemory不受JVM堆大小控制:

public static void main(String[] args) {
ByteBuffer.allocateDirect(10241024100); // 100MB
} ```

我們?cè)O(shè)置了JVM堆 64M限制法挨,然后在 直接內(nèi)存上分配了 100MB空間,程序執(zhí)行后直接報(bào)錯(cuò):Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory幅聘。接著我設(shè)置 -Xmx200M凡纳,程序正常結(jié)束。然后我修改配置: -Xmx64M -XX:MaxDirectMemorySize=200M帝蒿,程序正常結(jié)束荐糜。因此得出結(jié)論: 直接內(nèi)存DirectMemory的大小默認(rèn)為 -Xmx 的JVM堆的最大值,但是并不受其限制葛超,而是由JVM參數(shù) MaxDirectMemorySize單獨(dú)控制暴氏。接下來(lái)我們來(lái)證明直接內(nèi)存不是分配在JVM堆中。我們先執(zhí)行以下程序绣张,并設(shè)置 JVM參數(shù) -XX:+PrintGC答渔,

public static void main(String[] args) {         
 for(int i=0;i<20000;i++) {  
           ByteBuffer.allocateDirect(1024*100);  //100K  
      }  
  }  ```

  輸出結(jié)果如下:
[GC 1371K->1328K(61312K), 0.0070033 secs]    
[Full GC 1328K->1297K(61312K), 0.0329592 secs]    
[GC 3029K->2481K(61312K), 0.0037401 secs]     
[Full GC 2481K->2435K(61312K), 0.0102255 secs]

   我們看到這里執(zhí)行 GC的次數(shù)較少,但是觸發(fā)了 兩次 Full GC侥涵,原因在于直接內(nèi)存不受 GC(新生代的Minor GC)影響沼撕,只有當(dāng)執(zhí)行老年代的 Full GC時(shí)候才會(huì)順便回收直接內(nèi)存宋雏!而直接內(nèi)存是通過(guò)存儲(chǔ)在JVM堆中的DirectByteBuffer對(duì)象來(lái)引用的,所以當(dāng)眾多的 DirectByteBuffer對(duì)象從新生代被送入老年代后才觸發(fā)了 full gc务豺。
 
  再看直接在JVM堆上分配內(nèi)存區(qū)域的情況:

public static void main(String[] args) {
r(int i=0;i<10000;i++) {
ByteBuffer.allocate(1024*100); //100K
} ```

ByteBuffer.allocate 意味著直接在 JVM堆上分配內(nèi)存磨总,所以受 新生代的 Minor GC影響,輸出如下:

[GC 16023K->224K(61312K), 0.0012432 secs]
[GC 16211K->192K(77376K), 0.0006917 secs]
[GC 32242K->176K(77376K), 0.0010613 secs]
[GC 32225K->224K(109504K), 0.0005539 secs]
[GC 64423K->192K(109504K), 0.0006151 secs]
[GC 64376K->192K(171392K), 0.0004968 secs]
[GC 128646K->204K(171392K), 0.0007423 secs]
[GC 128646K->204K(299968K), 0.0002067 secs]
[GC 257190K->204K(299968K), 0.0003862 secs]
[GC 257193K->204K(287680K), 0.0001718 secs]
[GC 245103K->204K(276480K), 0.0001994 secs]
[GC 233662K->204K(265344K), 0.0001828 secs]
[GC 222782K->172K(255232K), 0.0001998 secs]
[GC 212374K->172K(245120K), 0.0002217 secs]

可以看到笼沥,由于直接在 JVM堆上分配內(nèi)存蚪燕,所以觸發(fā)了多次GC,且不會(huì)觸及 Full GC敬拓,因?yàn)閷?duì)象根本沒(méi)機(jī)會(huì)進(jìn)入老年代邻薯。

我想提個(gè)疑問(wèn),NIO中的DirectMemory和內(nèi)存文件映射同屬于直接緩沖區(qū)乘凸,但是前者和 -Xmx和-XX:MaxDirectMemorySize有關(guān)厕诡,而后者完全沒(méi)有JVM參數(shù)可以影響和控制,這讓我不禁懷疑兩者的直接緩沖區(qū)是否相同营勤,前 者指的是 JAVA進(jìn)程中的 native堆灵嫌,即涉及底層平臺(tái)如 win32的dll 部分,因?yàn)?C語(yǔ)言中的 malloc()分配的內(nèi)存就屬于 native堆葛作,不屬于 JVM堆寿羞,這也是DirectMemory能在一些場(chǎng)景中顯著提高性能的原因,因?yàn)樗苊饬嗽?native堆和jvm堆之間數(shù)據(jù)的來(lái)回復(fù)制赂蠢;而后者則是沒(méi)有經(jīng)過(guò) native堆绪穆,是由 JAVA進(jìn)程直接建立起 某一段虛擬地址空間和文件對(duì)象的關(guān)聯(lián)映射關(guān)系,參見(jiàn) Linux虛擬存儲(chǔ)器圖中的 “Memory mapped region for shared libraries” 區(qū)域虱岂,所以內(nèi)存映射文件的區(qū)域并不在JVM GC的回收范圍內(nèi)玖院,因?yàn)樗旧砭筒粚儆诙褏^(qū),卸載這部分區(qū)域只能通過(guò)系統(tǒng)調(diào)用 unmap()來(lái)實(shí)現(xiàn) (Linux)中第岖,而 JAVA API 只提供了 FileChannel.map 的形式創(chuàng)建內(nèi)存映射區(qū)域难菌,卻沒(méi)有提供對(duì)應(yīng)的 unmap(),讓人十分費(fèi)解蔑滓,導(dǎo)致要卸載這部分區(qū)域比較麻煩郊酒。

最后再試試通過(guò) DirectMemory來(lái)操作前面 內(nèi)存映射和基本通道操作的例子,來(lái)看看直接內(nèi)存操作的話键袱,程序的性能如何:

File file = new File("F:\\liq.pdf");  
FileInputStream in = new FileInputStream(file);  
FileChannel channel = in.getChannel();  
ByteBuffer buff = ByteBuffer.allocateDirect(1024);   
  
long begin = System.currentTimeMillis();  
while (channel.read(buff) != -1) {  
    buff.flip();  
    buff.clear();  
}  
long end = System.currentTimeMillis();  
System.out.println("time is:" + (end - begin));  

程序輸出為 312毫秒燎窘,看來(lái)比普通的NIO通道操作(468毫秒)來(lái)的快,但是比 mmap 內(nèi)存映射的 63秒差距太多了蹄咖,我想應(yīng)該不至于吧荠耽,通過(guò)修改;ByteBuffer buff = ByteBuffer.allocateDirect(1024); 為 ByteBuffer buff = ByteBuffer.allocateDirect((int)file.length()),即一次性分配整個(gè)文件長(zhǎng)度大小的堆外內(nèi)存比藻,最終輸出為 78毫秒铝量,由此可以得出兩個(gè)結(jié)論:1.堆外內(nèi)存的分配耗時(shí)比較大. 2.還是比mmap內(nèi)存映射來(lái)得慢,都不要說(shuō)通過(guò)mmap讀取數(shù)據(jù)的時(shí)候還涉及缺頁(yè)異常银亲、頁(yè)面調(diào)度的系統(tǒng)調(diào)用了慢叨,看來(lái)內(nèi)存映射文件確實(shí)NB啊,這還只是 86M的文件务蝠,如果上 G 的大小呢拍谐?
最后一點(diǎn)為 DirectMemory的內(nèi)存只有在 JVM執(zhí)行 full gc 的時(shí)候才會(huì)被回收,那么如果在其上分配過(guò)大的內(nèi)存空間馏段,那么也將出現(xiàn) OutofMemoryError轩拨,即便 JVM 堆中的很多內(nèi)存處于空閑狀態(tài)。

本來(lái)只想寫點(diǎn)內(nèi)存映射部分院喜,但是寫著寫著涉及進(jìn)來(lái)的知識(shí)多了點(diǎn)亡蓉,邊界不好把控啊。喷舀。砍濒。

我想補(bǔ)充下額外的一個(gè)知識(shí)點(diǎn),關(guān)于 JVM堆大小的設(shè)置是不受限于物理內(nèi)存硫麻,而是受限于虛擬內(nèi)存空間大小爸邢,理論上來(lái)說(shuō)是進(jìn)程的虛擬地址空間大小,但是實(shí)際上我們的虛擬內(nèi)存空間是有限制的拿愧,一 般windows上默認(rèn)在C盤杠河,大小為物理內(nèi)存的2倍左右。我做了個(gè)實(shí)驗(yàn):我機(jī)子是 64位的win7浇辜,那么理論上說(shuō)進(jìn)程虛擬空間是幾乎無(wú)限大券敌,物理內(nèi)存為4G,而我設(shè)置 -Xms5000M奢赂, 即在啟動(dòng)JAVA程序的時(shí)候一次性申請(qǐng)到超過(guò)物理內(nèi)存大小的5000M內(nèi)存陪白,程序正常啟動(dòng),而當(dāng)我加到 -Xms8000M的時(shí)候就報(bào)OOM錯(cuò)誤了膳灶,然后我修改增加 win7的虛擬內(nèi)存咱士,程序又正常啟動(dòng)了,說(shuō)明 -Xms 受限于虛擬內(nèi)存的大小轧钓。我設(shè)置-Xms5000M序厉,即超過(guò)了4G物理內(nèi)存,并在一個(gè)死循環(huán)中不斷創(chuàng)建對(duì)象毕箍,并保證不會(huì)被GC回收弛房。程序運(yùn)行一會(huì)后整個(gè)電腦 幾乎死機(jī)狀態(tài),即卡住了而柑,反映很慢很慢文捶,推測(cè)是發(fā)生了系統(tǒng)顛簸荷逞,即頻繁的頁(yè)面調(diào)度置換導(dǎo)致,說(shuō)明 -Xms -Xmx不是局限于物理內(nèi)存的大小粹排,而是綜合虛擬內(nèi)存了种远,JVM會(huì)根據(jù)電腦虛擬內(nèi)存的設(shè)置來(lái)控制。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末顽耳,一起剝皮案震驚了整個(gè)濱河市坠敷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌射富,老刑警劉巖膝迎,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異胰耗,居然都是意外死亡限次,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門宪郊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)掂恕,“玉大人,你說(shuō)我怎么就攤上這事弛槐“猛觯” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵乎串,是天一觀的道長(zhǎng)店枣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)叹誉,這世上最難降的妖魔是什么鸯两? 我笑而不...
    開(kāi)封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮长豁,結(jié)果婚禮上钧唐,老公的妹妹穿的比我還像新娘。我一直安慰自己匠襟,他們只是感情好钝侠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著酸舍,像睡著了一般帅韧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上啃勉,一...
    開(kāi)封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天忽舟,我揣著相機(jī)與錄音,去河邊找鬼。 笑死叮阅,一個(gè)胖子當(dāng)著我的面吹牛刁品,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帘饶,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼哑诊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了及刻?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤竞阐,失蹤者是張志新(化名)和其女友劉穎缴饭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骆莹,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡颗搂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了幕垦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丢氢。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖先改,靈堂內(nèi)的尸體忽然破棺而出疚察,到底是詐尸還是另有隱情,我是刑警寧澤仇奶,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布貌嫡,位于F島的核電站,受9級(jí)特大地震影響该溯,放射性物質(zhì)發(fā)生泄漏岛抄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一狈茉、第九天 我趴在偏房一處隱蔽的房頂上張望夫椭。 院中可真熱鬧,春花似錦氯庆、人聲如沸蹭秋。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)感凤。三九已至,卻和暖如春粒督,著一層夾襖步出監(jiān)牢的瞬間陪竿,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留族跛,地道東北人闰挡。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像礁哄,于是被迫代替她去往敵國(guó)和親长酗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容