轉(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)為例子飘千,看下圖:

此圖為 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)控制。