薛笛的短篇作品
发表于 CSDN网站
非文学 创作
![](https://img2.doubanio.com/view/page_note/small/public/p18558558-1.jpg)
论文模板的元素演示
又到一年毕业时,又到了面对让我焦头烂额的论文的时候了。由于毕业论文大概要写70-120页,如果之前只是码字,最后再集中排版虽然也不失为一种写作方式,但使我还是习惯在之前做足准备工作。原因是毕业论文不同于普通的文章,其格式有很多繁琐设定和要求,例如页面设置、页眉页脚、自动化目录、题注、索引,自动化的章节编号....,如果能够将这些动作先完成,相信对写作也有很多好处。
得到候捷老师《Word排版艺术》的启发,我也决定写一些有关硕士毕业论文排版方面的问题,把我正在用的各种排版细节贴出来给大家共享,尽管每个学校的排版要求不尽相同,但是希望各位能够有所启发就好。
1 对于“节”和奇偶页的说明
由于论文的正文部分是双面打印,所以章节的起始应该放到奇数页,这样不但可以与页眉的内容相吻合,还可以让观者在每读到新
的一章时都会从右手边另起一页。做到这一点需要让每一章独立成节,稍显麻烦,不过幸好毕业论文一般也没多少章?。
在页面设置里要设置“奇数页和偶数页不同” ,奇数页的页眉内容是章标题,偶数页的页眉内容是“黑龙江大学硕士学位论文”。当然,只有一页的节的页眉内容就是该页中所包含的章名。值得注意的是,尽管为了设置奇偶页的需要,我们将正文分为若干“节” ,我们应该保证正文部分的页码是顺序的阿拉伯数字,而不是一到新的一节就重新编号。只需要在“页脚工具栏>设置页码格式>页码编排”中选中序前节”即可。
2 各种格式的快捷键定义(定义在本文档中)
!我的正文=Alt+Z 我的题注样式 = Alt+T
章标题(标题1) = Alt+1
各节一级标题(标题2)= Alt+2
各节二级标题(标题3)= Alt+3
各节三级标题(标题4)= Alt+4
“!入目录章标题”没有快捷键,它的目的是不让被渲染的标题被自动编号,而“!不入目录章标”是为了那些不需要加入目录的标题(如像“目录”这样不想放入目录的标题)准备的。“妙用小格式P171)”指的是《Word排版艺术》171页遇到的问题的解决方案所用到的格式。
3 字体的一般规定
论文必须用宋体打印。正文用小4号宋体,1.5倍行距;各章题序及标题用小2号黑体,各节的一级题序及标题用小3号黑体,各节的
二级题序及标题用4号黑体,各节的三级题序及标题用小4号黑体;款项均采用小4号黑体;目录中各章题序及标题用小4号黑体,其余用小4号宋体。
4 其他有用元素的格式定义
4.1 题注
![](https://img2.doubanio.com/view/page_note/large/public/p18558558-1.jpg)
图 4 1功率与距离对应值
Fig.4-1 The relation between power and distance
(注意:由于这里题注是自动编号,所以第二个英文题注就不能自动插入了,否则数字就会继续增加,所以这里只能Dirty Work了
,等论文完成之后再手动加入)
表格的题注和这里大同小异,就不再赘述了。
4.2 交叉引用
引用第3篇文献[3],引用第2篇文献[2],以上可以看到[3]和[2]分别以超链接的方式引用了参考文献部分中的第3篇和第2篇(按住Ctrl,在鼠标单击引用即可到达文献所在位置)。事实上,参考文献部分的条目是用“格式>项目符号和编号”构建的,而对文献的引用是“插入>引用>交叉引用”得到的,也只有将文献条目编号,才能在交叉引用的界面中找到。
![](https://img3.doubanio.com/view/page_note/large/public/p18558558-2.jpg)
图 4 2交叉引用与项目编号之间的联系
下面如果我们将第二个参考文献删除,将产生两方面的影响,首先是参考文献条目编号,由于采用了“项目符号与编号”的方式自
4.3 关于页眉中字符下面的直线的设置
![](https://img3.doubanio.com/view/page_note/large/public/p18558558-3.jpg)
图 4 3 页眉下方的直线的设置
如果页眉中有文字,则页眉样式默认规定文字下方有一个普通的直线(当然通过设置我们也可以取消这个直线),而黑龙江大学论
文打印稿需要如上图所示的样子的直线,要做到这一点,我们需要只需要在页眉格式的边框和底纹中找到相应的直线图例就好了。
而将上图所示的上细下粗的直线改回来也很容易:只需要按照图 2 4的指示修改一下页眉样式的边框和底纹中的直线样式即可。需
要说明的是在图 2 4中所示的“更新以匹配选择”的选项,原本在“格式和样式”中显示的页眉样式是缺省的,在点选了这个选项
之后,所显示的页眉样式就被更新为正在编辑的页眉样式,否则我们做的改动也不能影响当前的直线样式。
![](https://img9.doubanio.com/view/page_note/large/public/p18558558-4.jpg)
图 4 4 设置页眉的底线样式(底纹和边框)
4.4 程序代码:
用一个文本框(插入>文本框)框起来的Courier New字体的代码
typedef struct metadata {
uint16_t time;// 发送时间
uint16_t power;// 发送功率
uint16_t freq;// 发送频率
} metadata_t;
动产生编号,所以原本的3号参考文献自动变成2号。第二,在引用上右键菜单中选择“更新域”之后,我们会发现原本指向3号文献的交叉引用变成了[2],而原本指向2号文献的交叉引用则变成[错误!未找到引用源]。此外,我们还可以在上图的“引用类型”中选择各种我们需要引用的元素类型,例如图 4 2就是一个对上图的引用。
![](https://img2.doubanio.com/view/page_note/large/public/p18558558-1.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18558558-2.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18558558-3.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18558558-4.jpg)
最后更新 2012-05-28 12:39:36
发表于 CSDN网站
非文学 创作
1 理由
我们做绝大多数事情之前都会有一个自认为合理的理由,才回去付诸行动。这次的动机是:我的笔记本装了Win7,但我的工作开发环境是linux,在回家之后我的代码没法在本机编译,于是回家之后代码之只能看不能改(改了也没法验证),着实是一件很郁闷的事情。曾经试过cygwin,但老实说使用过程中各种小毛病层出不穷,严重影响工作效率。也曾经冲动地想要干脆装个Linux替代Win7...
1 理由
我们做绝大多数事情之前都会有一个自认为合理的理由,才回去付诸行动。这次的动机是:我的笔记本装了Win7,但我的工作开发环境是linux,在回家之后我的代码没法在本机编译,于是回家之后代码之只能看不能改(改了也没法验证),着实是一件很郁闷的事情。曾经试过cygwin,但老实说使用过程中各种小毛病层出不穷,严重影响工作效率。也曾经冲动地想要干脆装个Linux替代Win7,但是公司邮件要用outlook,交换文档大多office文件,内网安全组件不支持linux….,“邪恶”的微软占据了我们大多数人的生活。《完全用Linux工作》的作者诚然很牛,但也只是能过着自给自足的生活,对外交流、公司环境下我们其实没办法“完全”用linux工作的。于是虚拟机就成了不二的选择。于是生活便轻松多了,我在虚拟机里放了代码,重编译了所需要的内部库,敲完make看着虚拟机努力的干活时心情还是相当不错的。
然而虚拟机也有问题,那就是本来我用虚拟机+SecureCRT已经打开很多窗口了,我往虚拟机里拷些文件进去还得用什么WinCap、FTP?拖来拽去好容易弄进去了,改完了想要弄出来还得费二遍事。不行,这不符合我们懒惰的个性。最终我在Win7和虚拟机之间搞了个共享文件夹,解决了所有的烦恼。
2 实做
对于这个问题来说,具体实做的过程是跟虚拟机软件有关的,现在主流虚拟机软件VBox和VMWare都是可以实现的,只是VBox不要钱所以我选择它(有谣传说Oracle会将其放弃掉?)。
步骤一:安装增强功能组件(Linux Guest Additions)
步骤二:lsmod | grep vboxvfs,确保加载了vboxvfs模块。如果没有,使用sudo modprobe vboxvfs加载。
步骤三:设备->分配数据空间->固定分配->选一个目录作为共享目录(如 F://vm/app),数据空间名称亦为app
步骤四:虚拟机中新建一个文件夹作为挂载点,sudo mkdir /home/xuedi/share。
步骤五:sudo mount -t vboxsf app /home/eddiexue/share。这一步每次开虚拟机都要挂一次,我们或者在启动项里面把这句加进去,或者干脆每次就不要关虚拟机了。VBox有“快速关闭”,关闭时能够记住上一次虚拟机的运行状态。所以我每次开虚拟机就直接恢复到之前的状态,方便好用:)
以上做法来源于网络,我找了很久、实验了很多方法才找到这个办法,作文以记之。
3 最后
在Windows装个Linux虚拟机,再为二者建立共享目录实现方便的数据交换,并非什么了不起的事情,只是为了用着方便,仅此而已。我想说的是,在公司上班,别头脑发热去把笔记本格了装linux或者弄个双系统,没必要,徒添烦恼而已。拎着笔记本到处开会也不方便,到时候接个投影仪放幻灯片都放不了多郁闷。PS:某次去会议室开会,看到桌上活动板下投影仪都俩插头,一个写ThinkPad,一个写Mac….你懂的…
最后更新 2012-05-28 12:36:52
展开
Java文件映射[mmap]全接触
(试发表)
试发表
非文学 创作
![](https://img2.doubanio.com/view/page_note/small/public/p18487141-1.jpg)
前言
我们在平时的工作中大多都会需要处理像下面这样基于Key-Value的数据:
![](https://img2.doubanio.com/view/page_note/large/public/p18487141-1.jpg)
其中UID是数据唯一标识,FIELD[1]是属性值。以QQ用户的Session为例,UID自然是QQ号,FIELD可能是性别、年龄、Session最后更新时间,上一个访问的URL等等。通常这些是要被频繁读写的,所以用C/C++的话通常的做法是使用共享内存划分出一大块内存,然后把它又分成同样大小的小块,用其中的一块或几块来保存一个UID和其所对应的数据,并配合一些索引和分配、回收块的算法等等。但是这些数据只是暂存,为了防止掉电或者硬件损坏等导致的数据丢失的问题,还需要用数据库或文件来作为持久化层,这就是我们常用的Cache+DB/File模型。但是这种模型如果用Java实现的话有两个问题:(1)在Cache层,虽然我们可以很方便地用一个HashMap<QQNum, UserSession>来保存数据,但是这样做的便利性是以消耗大量内存为代价的。因为中所周知Java里面万事万物皆对象,一个int和一个Integer的体积是差很多倍的。我曾经做过实验,读入一个有1000w行,每行两个数字的文件[大约170M]到一个HashMap<Integer,Integer>,该HashMap大约为400M。多么可怕的差距?!这使我很羡慕C/C++的可以使用操作系统的共享内存特性,可以很方便地直接对内存进行操作,那该多爽啊[暂不考虑JNI]。(2)在持久化方面,如果使用数据库的话,在海量的用户和读写操作面前,其性能将成为系统的主要瓶颈。使用文件的话又要自己写一套对于数据的增删改查操作方法,而且如果写的不通用的话就只能用在某个业务上,十分不经济。那有没有两全其美的办法,既能提供高效且经济[用类似共享内存的方式取代HashMap]的内存读写操作又能兼顾方便的持久化操作呢?JDK1.4引入的Mmap功能就是我们当前的选择。
1 功能简析
作为NIO的一个重要的功能,Mmap方法为我们提供了将文件的部分或全部映射到内存地址空间的能力,同当这块内存区域被写入数据之后[dirty],操作系统会用一定的算法把这些数据写入到文件中[这一过程java并没有提供API,后面会提到]。这样我们实际上就获得了间接操纵内存的能力,而且内存与文件之间的同步是由操作系统完成的,不用我们额外操心。也就是说,只要我们把内存数据块规划好[也就是实现一下C语言的SharedMemory功能],剩下的事情交给操作系统烦恼就好了。我们既获得了高效的读写操作能力,又解决了数据的持久化问题,多么理想的功能啊!但必须说明的是mmap毕竟不是数据库,不能很方便地提供事务功能、类似sql语句那样的查找功能,也不具备备份、回滚、迁移的能力,这些都要自己实现。不过这样显然不如放在数据库里放心,所以我们的经验是特别重要的数据还是存数据库,不太重要的、但是又访问量很大、读写操作多且需要持久化功能的数据是最适合使用mmap功能的。使用Java的mmapAPI代码框架如下所示:
(1)RandomAccessFile raf = new RandomAccessFile (File, "rw");
(2)FileChannel channel = raf.getChannel();
(3)MappedByteBuffer buff = channel.map(FileChannel.MapMode.READ_WRITE,startAddr,SIZE);
(4)buf.put((byte)255);
(5)buf.write(byte[] data)
其中最重要的就是那个buff,它是文件在内存中映射的标的物,通过对buff的read/write我们就可以间接实现对于文件的读写操作,当然写操作是操作系统帮忙完成的。
虽然mmap功能是如此的强大,但凡事都有局限,java的mmap瓶颈在哪里?使用mmap会遇到哪些问题和限制?要回到这些问题,还是需要先从mmap的实现入手。
2 实现原理
研究实现原理的最好方式就是阅读源码,由于SUN(或许不应该这样叫了?)开放了JDK源码,为我们的研究敞开了大门,这里我采用的是linux版的JDK1.6_u13的源码。
2.1 目标和方法
在查看Java源码之前,我首先google了一下mmap,结果发现mmap在linux下是一个系统调用:
void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off );
man了一下发现其功能描述和JavaAPI上说的差不多,难道JDK底层就是用这个东东实现的?马上动手写个程序然后STrace一下看看是不是使用了这个系统调用。这个测试程序应用的就是上面提到的那个程序框架,map了1G的文件,然后每次一个字节地往里面写数据,由于很简单这里就不贴出来了。结果如下:
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-2.jpg)
为简便起见中间的内容就忽略掉了,不过![](https://img3.doubanio.com/view/page_note/large/public/p18487141-2.jpg)
我们可以很清楚地看到mmap的操作就是打开[使用open系统调用]文件,然后mmap之,之后的操作都是对内存地址的直接操作,而操作系统负责把剩下的事情搞定了。于是可以大胆预言,java的实现是用JNI包装了的mmap()系统调用。其功能也应该和下图所示的内容保持一致。
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-3.jpg)
《APUE》中关于Mmap()系统调用的示意图
在经过上面的分析之后,我们已经有了初步的目标,那就是找到JavaMmap的C源码,看其使用了哪些系统调用。这样我们就可以更好地了解和控制JavaMmap的行为。
2.2 询源之旅
还是以下面这个代码框架为例,注意这里除了map文件的动作之外就只有写操作,因为mmap的读方法是读内存的,我们已经很清楚,所以这里我们只关心写操作。下面我们来一一分析和拆解这些类所使用到的系统调用。
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-4.jpg)
2.2.1 打开文件
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-5.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-6.jpg)
打开文件和建立FileChannel这两步应该只有一个open()系统调用。
2.2.2 Map文件、建立Buffer
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-7.jpg)
![](https://img1.doubanio.com/view/page_note/large/public/p18487141-8.jpg)
这一步骤由于我们一开始已经用STrace验证过了,没什么悬念地用到了mmap系统调用。但值得注意的是JDK只提供了建立文件/内存映射的方法,而没有给出解除映射关系的API。在FileChannelImpl.java中我们可以看到,解除映射的方法[在Unmapper中定义]是在创建MappedByteBuffer时嵌入到这个类里面的,在buffer被GC回收之前会调用Unmapper的unmap方法来解除文件到内存的映射关系。也就是说我们要想解除映射只能先把buffer置为null,然后祈祷GC赶紧起作用,实在等不及还可以用System.gc()催促一下GC赶快干活,不过后果是会引发FullGC。虽然要求开放解除映射关系的呼声很高,官方的回答是开放了会有这样那样的问题,总之是JDK7之前暂不会开放。其实这样的事情我们应该习惯才是,既然对象可以只new不delete,当然也可以map完不unmap啦,这件事只能说明要么开放unmap方法真的在技术上有困难,要么就是Sun对JVM太有自信[小玩笑,别当真]。不过我们还是发现了一个隐藏的系统调用:munmap();它用了解除映射关系,除此之外还有一些副作用,我们后面涉及到的时候再说。
2.2.3 对映射内存的写操作
![](https://img1.doubanio.com/view/page_note/large/public/p18487141-9.jpg)
但是由于Unsafe.java类所对应的unsafe.cpp的源码比较奇怪,里面并不是标准的C/C++源码,而是包含了很多宏和标记,同时里面也没有一个叫putByte()的方法(我们知道,JNI方法和其Java方法名字是有一定的命名规则的),看来代码是在编译过程中才会被替换成相应的函数定义,为此我还特意编译了一下jdk6_u13的Hotspot部分的源码(因为Unsafe.cpp是在Hotspot/src/share/vm/prems/里面),然后反编译生成的unsafe.o文件,被我找到了上图最后一行的定义:public native void putByte(java.lang.Object arg0, long arg1, byte arg2),看来还是要仔细分析一下unsafe.c文件找到相应的putByte定义:
![](https://img1.doubanio.com/view/page_note/large/public/p18487141-10.jpg)
unsafe.c的源码我们分两部分看,左边是我找到的我认为是putByte的实现:就是那个Declare_GetSetNative(Type),这个type可以是Byte,Short,Int等Java的基本类型,其作用是把一个基本类型的数据写到相应的内存地址中去,应该符合我们的要求,它的定义在右边。代码看起来很简单,就是定位到addr个内存地址上,然后把一个java类型的数据写到那个地址上。注意,虽然地址参数addr是一个long,但是addr_from_java把这个long变成了int,所以即便是在64位机器上也只能用使用2G这么大的地址空间,这也是Java无法一次Map到超过2G文件的原因。
2.3 Mmap实现小结
通过上面的分析,我们可以总结一下Java的Mmap的实际操作过程:使用mmap系统调用map一个文件的某一部分到内存,在要向里面写数据的时候就直接把以byte为单位的数据写到内存相应的地址(byte[]数组可以用一个for循环去写入)上去,并依靠操作系统的同步算法实现内存与映射文件之间的数据同步。至此我们已经基本搞清楚了JavaMmap的实现原理,不过在正式使用的过程中我们还遇到了一些奇怪的问题,下面我们就来逐一进行排查。
3 Mmap实现的Q&A
3.1 为什么Java的Mmap一次只能Map到2G大小的文件到内存?
这个问题已经在上面的分析中提到过了,这里不再赘述。
3.2 前面曾经提到,将已经Dirty的内存数据同步到文件的操作是操作系统控制的,有没有手动flush的方法?
这个问题的答案是有,不过使用这个方法会大幅度降低效率,应该慎用。Java实现的具体分析如下:
由前面的分析我们知道,putByte的实现方法是直接把数据写到共享内存,然后就不管了,所以数据什么时候写到文件是由操作系统算法决定的。理由是只有调用msync()系统调用之后,系统才会立刻把内存中的数据写入文件,否则即使是调用munmap()方法解除与map文件的关联也不能促使操作系统将共享内存中的数据写入文件(这个就由系统算法实现了)。不过java的mmapAPI也提供了立刻将内存数据刷到文件中的方法,其实内部就是用了msync系统调用。
![](https://img2.doubanio.com/view/page_note/large/public/p18487141-11.jpg)
3.3 为什么被映射的文件的时间戳总是不变?难道数据没有被写入吗?
在使用Java的Mmap功能的时候,我们会发现一个很诡异的问题,就是被映射的文件[也就是用open系统调用打开的那个文件]的时间戳居然是不变的?!我们知道,如果我们对文件使用了write(),或者用vim等编辑器对其进行了编辑,文件的时间戳是要发生改变的。而mmap对文件的写入操作是由操作系统完成的,难道操作系统写入之后就不改变时间戳吗?于是我写了一个C程序进行了验证,C程序的主要代码如下:
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int main(int argc, char *argv[])
{
int mapFile;
void* mapAddr;
void *filePath = "/data/log/storage_file.mmap";
void *p;
if ((mapFile = open(filePath, O_RDWR, "rw")) < 0){
printf("can't creat %s for writing\n", filePath);
exit(1);
}
if ((mapAddr = mmap(NULL, 1073741824, PROT_READ|PROT_WRITE, MAP_SHARED, mapFile, 0)) == NULL)
{
exit(1);
}
p=(void*)mapAddr;
*(unsigned char*)p='y';
return 0;
}
改程序执行结果如下:
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-12.jpg)
可以看出在写了一个字节到映射文件之后,该文件的最后修改时间确实变了。但是如果在数据写完之后、return之前先用pause()或者sleep()将进程停住,再观察文件的最后修改时间就会发现文件的时间戳是没有变化的。所以我们得到结论:在进程结束之后映射文件的时间戳会变成进程结束的时间。除此之外,经过实验我们还得到:在调用了munmap系统调用解除映射关系之后,文件的时间戳会变成调用munmap结束之后的时间,也就是说解除映射关系也会导致时间戳的变化,除上述两种情况之外,文件的时间戳是不会发生变化的。其实想想也能理解,操作系统也不会傻到每把一个byte写到文件就改一次时间戳,这样太影响效率。
3.4 Mmap和共享内存[SharedMemory]有何异同?
从man上来看,mmap似乎和共享内存[后面简称SM]有很多相同之处。相同点是共享的概念,mmap和SM都可以让操作系统划分出一块内存来共多个进程共享使用,也就是说多个进程可以对同一块内存进行读写操作,某一个进程的写入操作的结果可以被其他进程看到。不同点是mmap需要把内容写回到文件,所以还需要与文件打交道;而SM则是完全的内存操作,不涉及文件IO,效率上可能会好很多。还有就是SM使用的系统调用是shmget和shmctl。普通的JDK并没有提供SM的几口,而收费的JavaRTS提供了SM接口,也是通过JNI实现的。
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-13.jpg)
值得注意的是,无论是mmap还是SM,他们初始所向操作系统申请的那一块内存并不是一开始就能全额得到,操作系统会根据当前的内存使用状况为期分配一定大小的内存,而如果系统内存不足的话,操作系统还可以选择把mmap或SM中不常用的内存页换出来腾出地方给其他进程用。
3.5 为什么在使用了mmap之后,我用TOP/PS看到进程RSS越来越大?
这其实是一个很麻烦的问题,因为在一般运维监控的时候,我们都会很自然地选择Top或者PS看一下进程当前实用的物理内存是多少,以防进程内存占用过高导致系统崩溃。虽然TOP/PS的结果不是十分精确,但是大部分时候还是够用的。然而在使用了java的mmap之后我们发现,top和ps命令居然失效了。在我们的程序中map了一个3G大小的文件[这个文件自此之后一直没有变大],可是过几天之后[当然程序里面还有一些业务逻辑]却发现TOP命令的RSS字段居然变成了19G,更夸张的是过几天之后RSS的值仍然在不断增长,这已经远远超过了内存的实际大小,但此时系统的IO并不高,效率没有降低,也根本没用到swap。这就是说TOP/PS的结果是有问题的,此时的RSS已经不能正确标示当前进程所占用的物理内存了,而导致这个问题发生的原因又是什么呢?
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-14.jpg)
为此我查看了一下/proc/PID/smaps文件,因为这里面描述了进程地址空间的使用情况,我得到的结果如下:
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-15.jpg)
看到没?同一个文件被map了几次,smap文件中就有多少条记录项。于是我们可以大胆猜想,TOP/PS命令是否就是把smaps文件的中RSS做了一个简单的加法输出出来?后来经我们验证果然是这样的!也就是说文件被统一进程map的次数越多,smaps里面的对应项也就越多,所以TOP/PS的RSS字段值也就越大。
既然TOP/PS的值已经不可靠了,那么应该怎样获取使用了mmap的进程当前所占用的物理内存呢?google了一下排名最靠前的是一个叫做exmap的工具,不过那个工具不仅自己要重新编译,还需要重新编译内核[因为可能操作系统禁用了Module载入],最不能接受的是还是图形界面的,还有可能造成性能上的不稳定,这些限制使其在开发机上部署和使用变得不现实。后来又尝试使用mincore()系统调用,该系统调用的运作过程是,按照mmap的大小计算出一共需要多少内存页[例如每页4K],我们记为len,然后创建一个char* vector[len]数组,char[i]=0表示该页没在内存,而等于1的话表示该页在内存中。最后,该系统调用扫描一遍文件映射到内存中的部分,将结果写入vector数组中,我们可以根据其中1的个数来大概判断map文件中有多少物理页在内存中,不过遗憾的是这个系统调用貌似有点问题。举例来说,一个进程[P]映射了一个1G大小的文件,然后向里面写数据,这时候用mincore看到的结果是A,但是当P结束之后,再用mincore查看发现结果还是A,隔了一段时间之后再看结果还是A而不是0,即便是使用了munmap也没用。这就有些让人摸不着头脑。难道在内存空间够用的情况下操作系统把这部分内存一直保留着?但是当我把map文件删除之后用一个空文件替代再看结果就是0,再运行上面的程序结果和以前一样。看样子这个系统调用的结果不是很理想。所以现在也没有什么特别好的办法来解决这个问题。不过好在可以通过监控map文件大小来间接对mmap进行监控,如果map文件超过内存大小就要小心了,这时候系统性能就会狂降的。
4 后记
我们略带遗憾地结束了Java的Mmap之旅,最终也没能找到一个简单而准确的方法来查看当前进程的占用了多少物理内存[前提是不引入影响系统性能的组件和带界面的东西,简单的命令行结果最好]。欢迎发邮件与我联系,我们共同探讨。
![](https://img2.doubanio.com/view/page_note/large/public/p18487141-1.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-2.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-2.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-3.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-4.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-5.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-6.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-7.jpg)
![](https://img1.doubanio.com/view/page_note/large/public/p18487141-8.jpg)
![](https://img1.doubanio.com/view/page_note/large/public/p18487141-9.jpg)
![](https://img1.doubanio.com/view/page_note/large/public/p18487141-10.jpg)
![](https://img2.doubanio.com/view/page_note/large/public/p18487141-11.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-12.jpg)
![](https://img3.doubanio.com/view/page_note/large/public/p18487141-13.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-14.jpg)
![](https://img9.doubanio.com/view/page_note/large/public/p18487141-15.jpg)
最后更新 2012-05-24 19:28:21