本文鏈接: "Android mmap 文件映射到記憶體介紹" Android開發中,我們可能需要記錄一些文件。例如記錄log文件。如果使用流來寫文件,頻繁操作文件io可能會引起性能問題。 為了降低寫文件的頻率,我們可能會採用緩存一定數量的log,再一次性把它們寫到文件中。如果app異常退出,我們有可 ...
本文鏈接: Android mmap 文件映射到記憶體介紹
Android開發中,我們可能需要記錄一些文件。例如記錄log文件。如果使用流來寫文件,頻繁操作文件io可能會引起性能問題。
為了降低寫文件的頻率,我們可能會採用緩存一定數量的log,再一次性把它們寫到文件中。如果app異常退出,我們有可能會丟失記憶體中的log信息。
那麼有什麼比較穩妥的寫文件方式,既能降低io,又能儘可能地保證數據被寫入文件呢?
mmap簡介
mmap概念
mmap是一種記憶體映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁碟地址和進程虛擬地址空間中一段虛擬地址的一一對映關係。
特點:實現這樣的映射關係後,進程就可以採用指針的方式讀寫操作這一段記憶體,而系統會自動回寫臟頁面到對應的文件磁碟上,即完成了對文件的操作而不必再調用read,write等系統調用函數。相反,內核空間對這段區域的修改也直接反映用戶空間,從而可以實現不同進程間的文件共用。如下圖所示:
mmap記憶體映射原理
mmap記憶體映射的實現過程,總的來說可以分為三個階段:
應用進程啟動映射,在進程的虛擬地址空間中,尋找一段空閑的滿足要求的連續的虛擬地址作為映射區域;
調用系統函數mmap,實現文件物理地址和進程虛擬地址的一一映射;
應用進程對映射區域訪問,引發缺頁異常,實現文件內容到物理記憶體(主存)的拷貝。
mmap優缺點
只有一次數據拷貝:當發生缺頁異常時,直接將數據從磁碟拷貝到進程的用戶空間,跳過了頁緩存。
實現了用戶空間和內核空間的高效交互方式:兩空間的各自修改操作可以直接反映在映射的區域內,從而被對方空間及時捕捉。
提供進程間共用記憶體及相互通信的方式。
不管是父子進程還是無親緣關係的進程,都可以將自身用戶空間映射到同一個文件或匿名映射到同一片區域。從而通過各自對映射區域的改動,達到進程間通信和進程間共用的目的。
同時,如果進程A和進程B都映射了區域C,當A第一次讀取C時通過缺頁從磁碟複製文件頁到記憶體中;但當B再讀C的相同頁面時,雖然也會產生缺頁異常,但是不再需要從磁碟中複製文件過來,而可直接使用已經保存在記憶體中的文件數據。
mmap註意點
對於大文件而言,記憶體映射比普通IO流要快,小文件則未必;
不要經常調用MappedByteBuffer.force()方法,這個方法強制操作系統將記憶體中的內容寫入硬碟,所以如果你在每次寫記憶體映射文件後都調用force()方法,你就不能真正從記憶體映射文件中獲益,而是跟disk IO差不多。
讀寫記憶體映射文件是操作系統來負責的,因此,即使你的Java程式在寫入記憶體後就掛掉了,只要操作系統工作正常,數據就會寫入磁碟。
如果電源故障或者主機癱瘓,有可能記憶體映射文件還沒有寫入磁碟,意味著可能會丟失一些關鍵數據。
參考
- https://stackoverflow.com/questions/258091/when-should-i-use-mmap-for-file-access
- https://www.jianshu.com/p/187eada7b900
- https://juejin.im/post/5c3ec9ebf265da61223a93de#heading-0
- https://stackoverflow.com/questions/30180268/android-ndk-mmap-call-broken-on-32-bit-devices-after-upgrading-to-lollipop
- https://stackoverflow.com/questions/33897711/android-mmap-fails-with-out-of-memory
Android中的Binder也利用的mmap。Binder傳遞數據時,只需要複製一次,就能把數據傳遞到另一個進程中。參考Binder機制介紹
Android中使用mmap
Android中使用mmap,可以通過RandomAccessFile與MappedByteBuffer來配合。參考drone開發記錄 - log記錄工具
通過randomAccessFile.getChannel().map
獲取到MappedByteBuffer
。然後調用ByteBuffer的put方法添加數據。