On the NIO memory-mapped file with the principle DirectMemory

 Java class libraries in the NIO package relative to the IO package is a new feature is memory mapped files, the daily programming is not often used, but it is an ideal means to improve efficiency in handling large files. In this paper I would like to combine the main operating system in (OS) relevant knowledge to introduce the principle.

   In traditional file IO operations, we are calling the underlying operating system provides a standard IO system calls the function read (), write (), this time the process of this function (ie java process in JAVA) called by the current user mode switch to kernel mode, then the OS kernel code responsible for reading the data to the appropriate file IO buffer core and then copy the data from the kernel buffer to the IO process to the private address space, thus completed a IO operating. As for why bother to engage a core IO buffer with just one copy of the original data mess things you need twice the data copy it? I want to learn through the operating system or computer architecture knows, is done to reduce disk IO operations, in order to improve performance and to consider, because our program access generally come with locality, also known locality principle, here mainly refers to the spatial locality that we visited a certain period of data files, then the next piece of data is likely to visit the next, due to the speed of the disk IO operations better than direct access to memory a few slow magnitude, so the OS will be read-ahead cache more data files in a read () system call process based on the principle of locality in the kernel IO buffer, when the file data access will continue to directly copy the data in the buffer to the process private space, again to avoid inefficient disk IO operations. In JAVA package when we use the IO stream file operations, such as:

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

  JAVA virtual machine inside the OS will call the bottom of the read () system call to complete the operation, as described above, may in the second call in.read (), it is the return data directly from the kernel buffer (possibly through native stack do a transfer, because these functions are declared native, i.e. associated local internet, it may have to do a relay, such as win32 reads the data from the OS via the C code in C code, and then passed to memory JVM) . That being the case, JAVA why the IO package also provides a BufferedInputStream class it as a buffer. The key lies in two words, "system call"! When reading the OS kernel buffer data when it initiates a call to the operating system (through native C function call), at the expense of system calls is relatively high, context related to the user mode and kernel mode process of switching a series of operations, so we often packaged as follows:

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

As a result, every time we buf_in.read () time, BufferedInputStream we will automatically pre-reading more bytes of data according to the situation to an internal byte array buffer its own maintenance, so we can reduce the system calls times, so as to achieve its buffer. So to be clear is that the role of BufferedInputStream not reduce disk IO operations (the OS has helped us do), but rather to improve performance by reducing the number of system calls. Similarly BufferedOuputStream, BufferedReader / Writer is the same. In the C language library also has a similar implementation, as fread (), the C language is a function of the IO buffer, the effect BufferedInputStream () the same. 

    Here a brief reference to the source code under verification under JDK6 in BufferedInputStream of:

Copy the code
 1 public  
 2 class BufferedInputStream extends FilterInputStream {  
 3   
 4     private static int defaultBufferSize = 8192;  
 5   
 6     /** 
 7      * The internal buffer array where the data is stored. When necessary, 
 8      * it may be replaced by another array of 
 9      * a different size. 
10      */  
11     protected volatile byte buf[];  
12   /** 
13      * The index one greater than the index of the last valid byte in  
14      * the buffer.  
15      * This value is always 
16      * in the range <code>0</code> through <code>buf.length</code>; 
17      * elements <code>buf[0]</code>  through <code>buf[count-1] 
18      * </code>contain buffered input data obtained 
19      * from the underlying  input stream. 
20      */  
21     protected int count;  
22   
23     /** 
24      * The current position in the buffer. This is the index of the next  
25      * character to be read from the <code>buf</code> array.  
26      * <p> 
27      * This value is always in the range <code>0</code> 
28      * through <code>count</code>. If it is less 
29      * than <code>count</code>, then  <code>buf[pos]</code> 
30      * is the next byte to be supplied as input; 
31      * if it is equal to <code>count</code>, then 
32      * the  next <code>read</code> or <code>skip</code> 
33      * operation will require more bytes to be 
34      * read from the contained  input stream. 
35      * 
36      * @see     java.io.BufferedInputStream#buf 
37      */  
38     protected int pos;  
39   
40  /* 这里省略去 N 多代码 ------>>  */  
41   
42   /** 
43      * See 
44      * the general contract of the <code>read</code> 
45      * method of <code>InputStream</code>. 
46      * 
47      * @return     the next byte of data, or <code>-1</code> if the end of the 
48      *             stream is reached. 
49      * @exception  IOException  if this input stream has been closed by 
50      *              invoking its {@link #close()} method, 
51      *              or an I/O error occurs.  
52      * @see        java.io.FilterInputStream#in 
53      */  
54     public synchronized int read() throws IOException {  
55     if (pos >= count) {  
56         fill();  
57         if (pos >= count)  
58         return -1;  
59     }  
60     return getBufIfOpen()[pos++] & 0xff;  
61     } 
Copy the code

 We can see inside BufferedInputStream maintains a byte array byte [] buf to realize the function of the buffer, buf_in.read we call () method if there is to be a judge before returning the data, if the current index is not in the array buf within the valid index range, that is, if conditions are met, buf field maintenance of the buffer is not enough, and this time will be called fill inside the () method to fill, and fill () will be more pre-read data to the buffer buf array area to go and then return to the current byte of data, if the condition is not satisfied if they return data directly from the array buffer buf. Which cited getBufIfOpen () is the return of buf field. Under way, buf field declaration in the source code is protected volatile byte buf []; mainly to the visibility of the volatile memory by keyword guarantee buf array in multi-threaded environments.

   And the Java NIO-independent part of the memory map that so much space, mainly in order to pave the way to be, so as to build a body of knowledge, in order to better understand the advantages of memory-mapped files.  

   Memory-mapped files and standard IO operations said before the biggest difference is that it also Although the final data read from the disk, but it does not need to read data to the OS kernel buffer, but directly to the user process part of the region with the file object private address space mapping relationship established, as if reading directly from memory, as written document, of course, faster speed. To clarify this, we have a Linux operating system as an example, look: 

    

 This picture shows the Linux 2.X in the process virtual memory, which is the process virtual address space, if your machine is 32, then there is 2 ^ 32 = 4G virtual address space, we can see that there is a drawing area : "memory mapped region for shared libraries", this is the area at the time of memory-mapped files will be part of a virtual address and file objects of a certain period establishes mappings between this time and did not copy the data into memory, but when the process code is first referenced virtual address within this code, triggering a page fault exception, this time OS directly copy the relevant portions of the data file according to the mapping process to the user private space to go, when an operation of N when the data page repeats this OS pager operation. Attention to it, an important reason for the efficiency of the original memory-mapped file IO is higher than the standard because fewer copies the data to the OS kernel buffer This step (possibly less native heap turn this step).

   java provides three kinds of memory mapping modes, namely: a read-only (Readonly), write (READ_WRITE), private (Private), for read-only mode, if the program attempts to write, ReadOnlyBufferException exception is thrown; the second showed that the read-write mode to write or modify the content of the document by means of memory-mapped files, then is immediately reflected in the file to disk, and another process if you share the same mapping file, you will see an immediate change ! Rather than as a standard IO each process has its own kernel buffer, such as JAVA code, no execution flush IO output stream () or close () operation, then the changes to the file will not be updated to the disk to go, unless the process run ends; and finally a special mode uses a "copy on write" principle OS, that in the absence of a write operation occurred between multiple processes all share the same physical memory (process file of each virtual address pointing to the same piece of physical address), once a process is written, it will put the affected file copy data to a separate process of private buffer, not reflected in the physical file.

 

   In JAVA NIO can easily create a mapped region of memory, as follows:

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

Here we create a memory-mapped file region read-only mode, and then I came to the test under a performance advantage compared with ordinary NIO channels in operation, look at the following code:

Copy the code
 1 public class IOTest {  
 2     static final int BUFFER_SIZE = 1024;  
 3   
 4     public static void main(String[] args) throws Exception {  
 5   
 6         File file = new File("F:\\aa.pdf");  
 7         FileInputStream in = new FileInputStream(file);  
 8         FileChannel channel = in.getChannel();  
 9         MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_ONLY, 0,  
10                 channel.size());  
11   
12         byte[] b = new byte[1024];  
13         int len = (int) file.length();  
14   
15         long begin = System.currentTimeMillis();  
16   
17         for (int offset = 0; offset < len; offset += 1024) {  
18   
19             if (len - offset > BUFFER_SIZE) {  
20                 buff.get(b);  
21             } else {  
22                 buff.get(new byte[len - offset]);  
23             }  
24         }  
25   
26         long end = System.currentTimeMillis();  
27         System.out.println("time is:" + (end - begin));  
28   
29     }  
30 } 
Copy the code

Output is 63, that is, read 86M more files only need 78 milliseconds by means of memory-mapped files, I changed the channel operation NIO ordinary look:

Copy the code
 1 File file = new File("F:\\liq.pdf");  
 2 FileInputStream in = new FileInputStream(file);  
 3 FileChannel channel = in.getChannel();  
 4 ByteBuffer buff = ByteBuffer.allocate(1024);   
 5   
 6 long begin = System.currentTimeMillis();  
 7 while (channel.read(buff) != -1) {  
 8     buff.flip();  
 9     buff.clear();  
10 }  
11 long end = System.currentTimeMillis();  
12 System.out.println("time is:" + (end - begin));  
Copy the code

Output is 468 milliseconds, almost six times the gap, the larger the file, the greater the gap. So the memory mapped files are particularly suitable for operations on large files, JAVA is the limit must not exceed the maximum Integer.MAX_VALUE, that is about 2G, but we can operate the entire file to achieve through different parts of the sub-sub-map file (channel.map) of the goal of.

   Officially jdk document, direct memory-mapped files that belong to the JVM the buffer, the buffer can also be created directly by ByteBuffer.allocateDirect (), that is DirectMemory way. They compared based IO operations for the data copy overhead is less a middle buffer. At the same time they belong to the external JVM heap memory, unrestricted JVM heap memory size.

 

   Which DirectMemory default size is equivalent to the maximum JVM heap, in theory, the virtual address space is limited to the process, such as the 32-bit windows, each process has 4G virtual space to remove the outer 2G OS kernel reserves, minus JVM stack to the maximum, the remaining size is DirectMemory. By setting JVM parameters -Xmx64M, i.e. JVM max heap of 64M, and then execute the following procedure may not prove DirectMemory JVM heap size control:

1 public static void main(String[] args) {       
2     ByteBuffer.allocateDirect(1024*1024*100); // 100MB  
3 }  

 We set the JVM heap 64M limit, and then assign a 100MB memory space on a direct, immediate execution after an error: Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory. Then I set -Xmx200M, the program ends normally. Then I modify the configuration: -Xmx64M -XX: MaxDirectMemorySize = 200M, the program ends normally. Therefore concluded: DirectMemory direct memory size defaults to the maximum value of -Xmx JVM stack, but is not limited thereto, but rather is controlled by a separate JVM MaxDirectMemorySize parameters. Next we prove a direct memory is not allocated in the JVM heap. We first perform the following procedures, and set the JVM parameters -XX: + PrintGC,

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

 Output:

     [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]

   We have seen less frequently performed here GC, but fired twice Full GC, GC is not because of direct memory (the new generation of Minor GC) influence, only when the execution of Full GC old's recovery time will direct the way memory ! Memory is stored by direct reference to the object in DirectByteBuffer JVM heap, so when the object is sent to a large number of DirectByteBuffer years old from the new generation after triggering the full gc.

 Look directly allocated heap memory area in the JVM:

1 public static void main(String[] args) {         
2     for(int i=0;i<10000;i++) {  
3           ByteBuffer.allocate(1024*100);  //100K  
4     }
5 }  

ByteBuffer.allocate means directly in the JVM memory allocated on the heap, so Minor GC affected by the new generation, the output is as follows:

   
        [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]

   It can be seen as a direct allocation in the JVM heap memory, so triggering a number of GC, and does not touch the Full GC, because the objects have the chance to enter the old era.


   I want to ask a question, NIO in DirectMemory and memory mapped files directly belong to the same buffer, but the former and -Xmx and -XX: MaxDirectMemorySize, whereas the latter did not JVM parameters can influence and control, it makes me wonder both direct buffer is the same, the former refers to the process of JAVA native stack, i.e., as relates to win32 dll portion of the underlying platform, because the C language malloc () allocated memory belongs to native stack, not JVM stack, it DirectMemory can also reason in some scenarios significantly improve performance because it avoids copying data back and forth between the native heap and jvm heap; while the latter is without native heap, the process is directly established by the JAVA virtual address certain period association mapping between space and file objects, see FIG Linux virtual memory in "memory mapped region for shared libraries" region, the region in the memory-mapped files are not recovered within the range of JVM GC because it itself is not part of the heap area, uninstall this part of the region can only be called by the system unmap () is implemented (Linux), whereas JAVA API provides only FileChannel.map form to create a memory-mapped area, but did not provide the corresponding unmap (), people are very hard to understand, leading to uninstall this part of the region is too much trouble.

 

 Finally, by example DirectMemory try to operate the front and base channel memory mapping operations, to see if the direct memory operation, how to program performance:

   

Copy the code
 1 File file = new File("F:\\liq.pdf");  
 2 FileInputStream in = new FileInputStream(file);  
 3 FileChannel channel = in.getChannel();  
 4 ByteBuffer buff = ByteBuffer.allocateDirect(1024);   
 5   
 6 long begin = System.currentTimeMillis();  
 7 while (channel.read(buff) != -1) {  
 8     buff.flip();  
 9     buff.clear();  
10 }  
11 long end = System.currentTimeMillis();  
12 System.out.println("time is:" + (end - begin));
Copy the code

Program output is 312 milliseconds, it seems faster than normal NIO channel operation (468 milliseconds), but too many than 63 parsecs mmap memory mapping, I think it will not it, by modifying; ByteBuffer buff = ByteBuffer. allocateDirect (1024); as ByteBuffer buff = ByteBuffer.allocateDirect ((int) file.length ()), i.e., time allocation of heap memory entire outer length of the file size, the final output is 78 milliseconds, two conclusions can be drawn whereby : 1 heap memory outside the allocated time-consuming larger or slower than 2. mmap memory mapping, do not say by when reading data mmap also relates to the missing page exception paging system calls, it seems the memory map indeed file NB ah, this is just 86M documents, if G on the size of it?

  The last point is DirectMemory memory only when the implementation of full gc in the JVM will be recycled, so if a large memory space allocated through on it, it will also appear OutofMemoryError, even if many of JVM heap memory is idle.

  

  I had wanted to write about the memory map sections, read, read, involving knowledge came a little too much, to control the border is not good ah. . .

 

  Nyima, March 8 in the morning are fast 2:00, but think better than to stay up all night before the game play KOF Well, call it a day finished, and quickly went to sleep. . .

  

   I would like to add one extra knowledge next point, set on the JVM heap size is not limited to physical memory, but is limited by the size of the virtual memory space, in theory, is the size of the process virtual address space, but in fact our virtual memory space is limited, the C drive by default in Windows general, about twice the size of physical memory. I did an experiment: My machine is a 64-bit win7, so in theory the process virtual space is almost infinite, physical memory 4G, and I set -Xms5000M, JAVA program that starts when a one-time application to exceed the physical 5000M memory memory size, the program starts normally, but when I added -Xms8000M when they reported OOM error, and then I modify win7 increase virtual memory, the program has started normally, indicating -Xms limited by the size of virtual memory . I set -Xms5000M, that is more than 4G of physical memory, and continue to create objects in an infinite loop, and guaranteed not to be recovered GC. After a while the program is running almost the entire computer crash state, that stuck, reflecting very slowly, presumably occurred thrashing that frequently lead to the replacement of paging, indicating -Xms -Xmx is not limited to the size of physical memory, but a combination of virtual memory, JVM will be controlled according to the computer's virtual memory settings.

 

Transfer: http://blog.csdn.net/fcbayernmunchen/article/details/8635427

Guess you like

Origin www.cnblogs.com/itplay/p/10944072.html