本文鏈接: 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方法添加數據。