02_內核中的 pagecache、mmap 作用、java 文件系統 io、nio、內存中緩存區作用

本節主要學習內核中 pagecache 頁緩存、mmap 內存映射、Java 文件系統 BIO、NIO、內存緩存區的作用。

1. pagecache

image-20210122012652367

本節主要驗證 Java 程序寫入文件時,pagecache 的使用、io、nio、內存中緩存區的作用。

Java 程序代碼:

import org.junit.Test;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class OSFileIO {
    
    static byte[] data = "123456789\n".getBytes();
    
    static String path = "/root/testfileio/out.txt";
    
    public static void main(String[] args) throws Exception {
        
        switch (args[0]) {
            case "0":
                testBasicFileIO();
                break;
            case "1":
                testBufferedFileIO();
                break;
            case "2":
                testRandomAccessFileWrite();
            case "3":
                //                whatByteBuffer();
            default:
            
        }
    }
    
    //最基本的file寫
    public static void testBasicFileIO() throws Exception {
        File file = new File(path);
        FileOutputStream out = new FileOutputStream(file);
        while (true) {
            Thread.sleep(10);
            out.write(data);
            
        }
        
    }
    
    //測試buffer文件IO
    //  jvm  8kB   syscall  write(8KBbyte[])
    public static void testBufferedFileIO() throws Exception {
        File file = new File(path);
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
        while (true) {
            Thread.sleep(10);
            out.write(data);
        }
    }
    
    //測試文件NIO
    public static void testRandomAccessFileWrite() throws Exception {
        
        RandomAccessFile raf = new RandomAccessFile(path, "rw");
        
        raf.write("hello mashibing\n".getBytes());
        raf.write("hello seanzhou\n".getBytes());
        System.out.println("write------------");
        System.in.read();
        
        raf.seek(4);
        raf.write("ooxx".getBytes());
        
        System.out.println("seek---------");
        System.in.read();
        
        FileChannel rafchannel = raf.getChannel();
        //mmap  堆外  和文件映射的   byte  not  objtect
        MappedByteBuffer map = rafchannel.map(FileChannel.MapMode.READ_WRITE, 0, 4096);
        
        map.put("@@@".getBytes());  //不是系統調用  但是數據會到達 內核的pagecache
        //曾經我們是需要out.write()  這樣的系統調用,才能讓程序的data 進入內核的pagecache
        //曾經必須有用戶態內核態切換
        //mmap的內存映射,依然是內核的pagecache體系所約束的!!!
        //換言之,丟數據
        //你可以去github上找一些 其他C程序員寫的jni擴展庫,使用linux內核的Direct IO
        //直接IO是忽略linux的pagecache
        //是把pagecache  交給了程序自己開闢一個字節數組當作pagecache,動用代碼邏輯來維護一致性/dirty。。。一系列複雜問題
        
        System.out.println("map--put--------");
        System.in.read();
        
        //        map.force(); //  flush
        
        raf.seek(0);
        
        ByteBuffer buffer = ByteBuffer.allocate(8192);
        //        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        
        int read = rafchannel.read(buffer);   //buffer.put()
        System.out.println(buffer);
        buffer.flip();
        System.out.println(buffer);
        
        for (int i = 0; i < buffer.limit(); i++) {
            Thread.sleep(200);
            System.out.print(((char) buffer.get(i)));
        }
        
    }
    
    @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);
        
    }
    
}

這個程序提供了三個方法:

  • testBasicFileIO() 方法是直接通過 FileOutputStream 往 out.txt 文件中寫數據,每次寫入都會調用系統調用
  • testBufferedFileIO() 方法是直接通過 BufferedOutputStream 往 out.txt 文件中寫數據,它在 jvm 堆內存上開闢了 8kb 的緩存區,當緩存區滿了再調用系統調用
  • testRandomAccessFileWrite() 方法是創建一個 RandomAccessFile 隨機訪問文件,有直接寫入數據到文件,也有通過 FileChannel 調用 map 方法獲取 MappedByteBuffer,然後讀取數據

修改內核:

# 後臺髒頁緩存寫入磁盤的比例
vm.dirty_background_ratio = 90
vm.dirty_background_bytes = 1048576
# 前臺頁緩存髒的比例,超過這個比例,程序會先暫停,內核先把 pagecache 寫入磁盤
vm.dirty_ratio = 90
vm.dirty_bytes = 1048576
vm.dirty_writeback_centisecs = 5000
vm.dirty_expire_centisecs = 30000

驗證下 Java 程序調用內核的系統調用:

# 編輯一個 shell 文件 mysh
[root@ecs-02 testfileio]# cat mysh
rm -fr *out*
javac OSFileIO.java
strace -ff -o out java OSFileIO $1

# 執行腳本
[root@ecs-02 testfileio]# ./mysh 0

# 執行命令觀察文件和頁緩存加載的情況變化
[root@ecs-02 testfileio]# ll
總用量 11780
-rwxr-xr-x 1 root root       68 1月  19 20:07 mysh
-rwxr-xr-x 1 root root       87 1月  21 16:06 mysh2
-rw-r--r-- 1 root root     4084 1月  21 17:48 OSFileIO.class
-rw-r--r-- 1 root root     4766 1月  19 20:05 OSFileIO.java
-rw-r--r-- 1 root root    16930 1月  21 18:47 out.883730
-rw-r--r-- 1 root root   149446 1月  21 18:47 out.883731
-rw-r--r-- 1 root root   552261 1月  21 18:47 out.883732
-rw-r--r-- 1 root root      935 1月  21 18:47 out.883733
-rw-r--r-- 1 root root     1126 1月  21 18:47 out.883734
-rw-r--r-- 1 root root     1914 1月  21 18:47 out.883735
-rw-r--r-- 1 root root   135995 1月  21 18:47 out.883736
-rw-r--r-- 1 root root   229724 1月  21 18:47 out.883737
-rw-r--r-- 1 root root    17420 1月  21 18:47 out.883738
-rw-r--r-- 1 root root     1049 1月  21 18:47 out.883739
-rw-r--r-- 1 root root 10855959 1月  21 18:47 out.883740
-rw-r--r-- 1 root root    10058 1月  21 18:47 out.883741
-rw-r--r-- 1 root root     2437 1月  21 18:47 out.883772
-rw-r--r-- 1 root root     1195 1月  21 18:47 out.883891
-rw-r--r-- 1 root root     1200 1月  21 00:43 SocketClient.class
-rw-r--r-- 1 root root      822 1月  21 00:42 SocketClient.java
-rw-r--r-- 1 root root     2236 1月  21 00:27 SocketIO.class
-rw-r--r-- 1 root root     1204 1月  21 00:22 SocketIO.java
-rw-r--r-- 1 root root     3573 1月  21 22:36 SocketIOPropertites.class
-rw-r--r-- 1 root root     3623 1月  21 22:36 SocketIOPropertites.java
-rw-r--r-- 1 root root     2890 1月  21 22:05 SocketNIO.java
[root@ecs-02 testfileio]# pcstat out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 8882790        | 2169       | 2169      | 100.000 |
+---------+----------------+------------+-----------+---------+

編寫一個 shell 腳本,執行一個 Java 進程,並且使用 strace 命令追蹤這個進程調用系統調用的信息,並且輸出信息到以 out 開頭的文件。

pcstat 是一個查看文件的 pagecache 的使用情況的工具。

可以看到這個 out.txt 文件在不斷的變大,pcstat 展示出它的 Pages 數不斷變大,以及它的 page 頁和 cache 頁的佔比情況。

隔一段時間再看這個 out.txt 文件的 pagecache 情況:

[root@ecs-02 testfileio]# pcstat out.txt
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 165076610      | 40302      | 28760     | 071.361 |
+---------+----------------+------------+-----------+---------+

發現它的 Pages 和 Cached 都在不斷的增加,Percent 比例開始下降了。這是因爲這個文件不斷的增長,它在內存中的 page 和 cache 不斷變大,但是 cache 的增長速率很慢。

關閉 Java 程序,將 out.txt 文件改名爲 ooxx.txt。

再次啓動 mysh 文件:

[root@ecs-02 testfileio]# ./mysh 1

在另一個窗口查看 out.txt 和 ooxx.txt 文件的變化和它們的 pagecacache 信息:

[root@ecs-02 testfileio]# ll -h ; pcstat out.txt ; pcstat ooxx.txt
總用量 1.3G
-rwxr-xr-x 1 root root   68 1月  19 20:07 mysh
-rwxr-xr-x 1 root root   87 1月  21 16:06 mysh2
-rw-r--r-- 1 root root 292M 1月  22 19:04 ooxx.txt
-rw-r--r-- 1 root root 4.0K 1月  22 19:14 OSFileIO.class
-rw-r--r-- 1 root root 4.7K 1月  19 20:05 OSFileIO.java
-rw-r--r-- 1 root root  17K 1月  22 19:14 out.886277
-rw-r--r-- 1 root root 5.4M 1月  22 19:14 out.886278
-rw-r--r-- 1 root root 3.2K 1月  22 19:14 out.886279
-rw-r--r-- 1 root root  905 1月  22 19:14 out.886280
-rw-r--r-- 1 root root 1.1K 1月  22 19:14 out.886281
-rw-r--r-- 1 root root 1001 1月  22 19:14 out.886282
-rw-r--r-- 1 root root  23K 1月  22 19:14 out.886283
-rw-r--r-- 1 root root  65K 1月  22 19:14 out.886284
-rw-r--r-- 1 root root 6.7K 1月  22 19:14 out.886285
-rw-r--r-- 1 root root 1019 1月  22 19:14 out.886286
-rw-r--r-- 1 root root  34K 1月  22 19:14 out.886287
-rw-r--r-- 1 root root  980 1月  22 19:14 out.886288
-rw-r--r-- 1 root root 676M 1月  22 19:14 out.txt
-rw-r--r-- 1 root root 1.2K 1月  21 00:43 SocketClient.class
-rw-r--r-- 1 root root  822 1月  21 00:42 SocketClient.java
-rw-r--r-- 1 root root 2.2K 1月  21 00:27 SocketIO.class
-rw-r--r-- 1 root root 1.2K 1月  21 00:22 SocketIO.java
-rw-r--r-- 1 root root 3.5K 1月  21 22:36 SocketIOPropertites.class
-rw-r--r-- 1 root root 3.6K 1月  21 22:36 SocketIOPropertites.java
-rw-r--r-- 1 root root 2.9K 1月  21 22:05 SocketNIO.java
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 708227072      | 172907     | 172907    | 100.000 |
+---------+----------------+------------+-----------+---------+
+----------+----------------+------------+-----------+---------+
| Name     | Size (bytes)   | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| ooxx.txt | 306016090      | 74711      | 30925     | 041.393 |
+----------+----------------+------------+-----------+---------+

過上 10 秒再看下:

[root@ecs-02 testfileio]# ll -h ; pcstat out.txt ; pcstat ooxx.txt
總用量 2.4G
-rwxr-xr-x 1 root root   68 1月  19 20:07 mysh
-rwxr-xr-x 1 root root   87 1月  21 16:06 mysh2
-rw-r--r-- 1 root root 292M 1月  22 19:04 ooxx.txt
-rw-r--r-- 1 root root 4.0K 1月  22 19:14 OSFileIO.class
-rw-r--r-- 1 root root 4.7K 1月  19 20:05 OSFileIO.java
-rw-r--r-- 1 root root  17K 1月  22 19:14 out.886277
-rw-r--r-- 1 root root 8.7M 1月  22 19:14 out.886278
-rw-r--r-- 1 root root 4.1K 1月  22 19:14 out.886279
-rw-r--r-- 1 root root  905 1月  22 19:14 out.886280
-rw-r--r-- 1 root root 1.1K 1月  22 19:14 out.886281
-rw-r--r-- 1 root root 1001 1月  22 19:14 out.886282
-rw-r--r-- 1 root root  23K 1月  22 19:14 out.886283
-rw-r--r-- 1 root root  65K 1月  22 19:14 out.886284
-rw-r--r-- 1 root root 6.7K 1月  22 19:14 out.886285
-rw-r--r-- 1 root root 1019 1月  22 19:14 out.886286
-rw-r--r-- 1 root root  53K 1月  22 19:14 out.886287
-rw-r--r-- 1 root root  980 1月  22 19:14 out.886288
-rw-r--r-- 1 root root 1.1G 1月  22 19:14 out.txt
-rw-r--r-- 1 root root 1.2K 1月  21 00:43 SocketClient.class
-rw-r--r-- 1 root root  822 1月  21 00:42 SocketClient.java
-rw-r--r-- 1 root root 2.2K 1月  21 00:27 SocketIO.class
-rw-r--r-- 1 root root 1.2K 1月  21 00:22 SocketIO.java
-rw-r--r-- 1 root root 3.5K 1月  21 22:36 SocketIOPropertites.class
-rw-r--r-- 1 root root 3.6K 1月  21 22:36 SocketIOPropertites.java
-rw-r--r-- 1 root root 2.9K 1月  21 22:05 SocketNIO.java
+---------+----------------+------------+-----------+---------+
| Name    | Size (bytes)   | Pages      | Cached    | Percent |
|---------+----------------+------------+-----------+---------|
| out.txt | 1152053248     | 281263     | 236038    | 083.921 |
+---------+----------------+------------+-----------+---------+
+----------+----------------+------------+-----------+---------+
| Name     | Size (bytes)   | Pages      | Cached    | Percent |
|----------+----------------+------------+-----------+---------|
| ooxx.txt | 306016090      | 74711      | 0         | 000.000 |
+----------+----------------+------------+-----------+---------+

能夠得出幾個結論:

  1. Java 的 testBufferedFileIO() 方法寫入文件的速度快,因爲它在 jvm 的堆上使用 8KB 的緩衝區,相比於 testBasicFileIO() 方法減少了寫入磁盤的系統調用,速度更快
  2. out.txt文件不斷變大的過程中,它的 pagecache 會把內存中已經打開的 ooxx.txt 文件佔用的 pagecache 都給擠掉。

還有一個點,內存中的 pagecache 當被程序創建、修改之後,就會變成 dirty,那麼觸發系統的 page diry 配置參數限制時,會先把 page 寫入磁盤,對應的 dirty 就會被抹掉,然後把這個 pagecache 淘汰掉,如果這個 pagecache 還被引用就會把放入到 swap 分區。

2. mmap

再次執行 mysh 文件,使用 testRandomAccessFileWrite() 方法:

# 傳入參數 2,調用程序的 testRandomAccessFileWrite() 方法
[root@ecs-02 testfileio]# ./mysh 2
write------------

開啓另一個 ssh 窗口,查看 out.txt 文件信息:

[root@ecs-02 testfileio]# cat out.txt
hello mashibing
hello seanzhou

此時程序在阻塞,我們輸入回車

# 傳入參數 2,調用程序的 testRandomAccessFileWrite() 方法
[root@ecs-02 testfileio]# ./mysh 2
write------------

seek---------

注意輸入回車之後,程序會執行隨機讀寫 seek(4) 方法,它會執行到它內部維護這個,它會跳轉到這個文件在內存中 pagecache 指定位置,然後從這個地址上寫入數據。

並且程序開始執行獲取 FileChannel,以及執行它的 map 方法返回 MappedByteBuffer map,在這個 map 上執行 map.put("@@@".getBytes()) 方法。

再查看 out.txt 文件信息:

[root@ecs-02 testfileio]# cat out.txt
hellooxxshibing
hello seanzhou

可以發現文件的第一行 hell後邊 4 個字符被修改了,變成了 ooxx。

Java 程序中的 FileChannel 的 map() 方法返回的是這個文件對應的內核中內存 pagecache 的直接映射,它不依賴與 java 進程中堆區以及裏邊的 jvm 堆內區。

對於它的操作不需要經過 java 進程堆、jvm 堆區,不需要用戶態與內核態的切換。

Mmap 不是系統調用,但是數據會到達內核的 pagecache,曾經我們是需要 out.write() 這樣的系統調用,才能讓程序的 data 進入內核的 pagecache,曾經必須有用戶態內核態切換,mmap 的內存映射,依然是內核的 pagecache 體系所約束的!!!

換言之,丟數據。你可以去github上找一些 其他C程序員寫的jni擴展庫,使用linux內核的Direct IO,直接IO是忽略linux的pagecache 是把pagecache 交給了程序自己開闢一個字節數組當作pagecache,動用代碼邏輯來維護一致性/dirty。。。一系列複雜問題

<img src="http://picture.kaisesai.com/image-20210122192532089.png" alt="image-20210122192532089" />

3. java 文件系統 io、nio、內存中緩存區作用

Java 1.4 中的 ByteBuffer 是一個字節緩存區,有三個屬性:position、limit、capacity

<img src="http://picture.kaisesai.com/image-20210122194835056.png" alt="image-20210122194835056" style="zoom:50%;" />

它的操作方法:

  • put() 添加數據
  • flip() 反轉,用於讀寫交替
  • get() 獲取字節數據
  • compact() 壓縮
  • clear() 清理
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章