xiaoguozi's Blog
Pay it forword - 我并不觉的自豪,我所尝试的事情都失败了······习惯原本生活的人不容易改变,就算现状很糟,他们也很难改变,在过程中,他们还是放弃了······他们一放弃,大家就都是输家······让爱传出去,很困难,也无法预料,人们需要更细心的观察别人,要随时注意才能保护别人,因为他们未必知道自己要什么·····
经典语录1:

哈佛有一个著名的理论:人的差别在于业余时间,而一个人的命运决定于晚上8点到10点之间。每晚抽出2个小时的时间用来阅读、进修、思考或参加有意的演讲、讨论,你会发现,你的人生正在发生改变,坚持数年之后,成功会向你招手。不要每天抱着QQ/MSN/游戏/电影/肥皂剧……奋斗到12点都舍不得休息,看就看一些励志的影视或者文章,不要当作消遣;学会思考人生,学会感悟人生,慢慢的,你的人生将会发生变化……

经典语录2: 

无论你的收入是多少,记得分成五份进行规划投资:增加对身体的投资,让身体始终好用;增加对社交的投资,扩大你的人脉;增加对学习的投资,加强你的自信;增加对旅游的投资,扩大你的见闻;增加对未来的投资,增加你的收益。好好规划落实,你会发现你的人生逐步会有大量盈余。

经典语录3: 

过去的一页,能不翻就不要翻,翻落了灰尘会迷了双眼。有些人说不出哪里好,但就是谁都替代不了! 那些以前说着永不分离的人,早已经散落在天涯了。收拾起心情,继续走吧,错过花,你将收获雨,错过这一个,你才会遇到下一个。

经典语录4: 

被人误解的时候能微微的一笑,这是一种素养;受委屈的时候能坦然的一笑,这是一种大度;吃亏的时候能开心的一笑,这是一种豁达;无奈的时候能达观的一笑,这是一种境界;危难的时候能泰然一笑,这是一种大气;被轻蔑的时候能平静的一笑,这是一种自信;失恋的时候能轻轻的一笑,这是一种洒脱。

经典语录5: 

人生途中,有些是无法逃避的,比如命运;有些是无法更改的,比如情缘;有些是难以磨灭的,比如记忆;有些是难以搁置的,比如爱恋……与其被动地承受,不如勇敢地面对;与其鸟宿檐下,不如击翅风雨;与其在沉默中孤寂,不如在抗争中爆发……路越艰,阻越大,险越多,只要走过去了,人生就会更精彩。

经典语录6: 

你改变不了环境,但你可以改变自己;你改变不了事实,但你可以改变态度;你改变不了过去,但你可以改变现在;你不能控制他人,但你可以掌握自己;你不能预知明天,但你可以把握今天;你不可以样样顺利,但你可以事事尽心;你不能延伸生命的长度,但你可以决定生命的宽度。

经典语录7: 

魅力女人:1、善于发现生活里的美。2、养成看书的习惯。3、拥有品位。4、跟有思想的人交朋友。5、远离泡沫偶像剧。6、学会忍耐与宽容。7、培养健康的心态,重视自己的身体。8、离开任何一个男人,都会活得很好。9、有着理财的动机,学习投资经营。10、尊重感情,珍惜缘分。

经典语录8: 

愚人向远方寻找快乐,智者则在自己身旁培养快乐。生活里的每一个细节都蕴藏着快乐,只是在于你是否感受到了而已。快乐着的人,每一件事,每一个人身上,他都能发现能令自己欢悦的因素来,并让快乐扩张,鼓舞和影响了周围的人。

经典语录9: 

【给自己安慰的10句温馨话】1、最重要的是今天的心;2、别总是自己跟自己过不去;3、用心做自己该做的事;4、不要过于计较别人评价;5、每个人都有自己的活法;6、喜欢自己才会拥抱生活;7、不必一味讨好别人;8、木已成舟便要顺其自然;9、不妨暂时丢开烦心事;10、自己感觉幸福就是幸福。

经典语录10: 

没有永远的缘份,没有永远的生命,我们所能拥有的,可能只是平凡的一生。然而因为有你,生命便全然不同,不用誓言,不必承诺,我们只需依了爱缘,以目光为媒,印证三生石上的约定,便牵了手,不必紧握,却永不放松,以自己设计的爱的程式,去演绎一种精典的永恒。

经典语录11: 

我们之所以会心累,就是常常徘徊在坚持和放弃之间,举棋不定。我们之所以会烦恼,就是记性太好,该记的,不该记的都会留在记忆里。我们之所以会痛苦,就是追求的太多。我们之所以不快乐,就是计较的太多,不是我们拥有的太少,而是我们计较的太多。

经典语录12: 

男人吸引女人的10个特质:1.真实 2.深刻 3.胸怀 4.敢为 5.风度 6.机灵 7.幽默 8.进取 9.浪漫 10.冒险.女人吸引男人的10个特点:1.温柔 2.知性 3.直性 4.涵养 5.朦胧 6.小动作 7.勤于家事 8.肤白 9.性感着装 10.香氛

经典语录13: 

真正的爱,是接受,不是忍受;是支持,不是支配;是慰问,不是质问;真正的爱,要道谢也要道歉。要体贴,也要体谅。要认错,也好改错;真正的爱,不是彼此凝视,而是共同沿着同一方向望去。其实,爱不是寻找一个完美的人。而是,要学会用完美的眼光,欣赏一个并不完美的人。

经典语录14: 

身边总有些人,你看见他整天都开心,率真得像个小孩,人人都羡慕他;其实,你哪里知道:前一秒人后还伤心地流着泪的他,后一秒人前即刻洋溢灿烂笑容。他们其实没有能力独处,夜深人静时,总坐在窗前对着夜空冥想失意的苦楚。他们就像向日葵,向着太阳的正面永远明媚鲜亮,在照不到的背面却将悲伤深藏。

经典语录15: 

生命中,有些人来了又去,有些人去而复返,有些人近在咫尺,有些人远在天涯,有些人擦身而过,有些人一路同行。或许在某两条路的尽头相遇,结伴同行了一段路程,又在下一个分岔路口道别。无论如何,终免不了曲终人散的伤感。远在天涯的朋友:或许已是遥远得无法问候,但还是谢谢您曾经的结伴同行。

经典语录16: 

爱情很简单,因为每个人都会说:“我爱你,会为你付出一切!”,爱情很难,因为没有多少人做到了他的承诺。如果真心爱一个人,不承诺也会去爱;如果不爱一个人,曾经承诺也会背叛。

经典语录17: 

【你最后悔什么】某杂志对全国60岁以上的老人抽样调查:第一名:75%的人后悔年轻时努力不够,导致一事无成。第二名:70%的人后悔在年轻的时候选错了职业。第三名:62%的人后悔对子女教育不当。第四名:57%的人后悔没有好好珍惜自己的伴侣。第五名:49%的人后悔没有善待自己的身体。

经典语录18: 

【做人十心机】⒈做人不能太单纯 适度伪装自己 ⒉凡事留余地 要留退路 ⒊话不说绝口无遮拦难成大事 ⒋成熟而不世故 ⒌心态好 想得开活得不累 ⒍懂方圆之道:没事不惹事,来事不怕事 ⒎不可少二礼:礼仪与礼物 ⒏人在江湖飘 防挨朋友刀 ⒐偶尔"势利眼" 寻可靠伙伴 ⒑放下面子来做人。

经典语录19: 

人生旅途中,总有人不断地走来,有人不断地离去。当新的名字变成老的名字,当老的名字渐渐模糊,又是一个故事的结束和另一个故事的开始。在不断的相遇和错开中,终于明白:身边的人只能陪着自己走过或近或远的一程,而不能伴自己一生;陪伴一生的是自己的名字和那些或清晰或模糊的名字所带来的感动。

经典语录20: 

从现在开始,聪明一点,不要问别人想不想你,爱不爱你?若是要想你或者爱你自然会对你说,但是从你的嘴里说出来,别人会很骄傲和不在乎你。再也不要太在意一些人,太在乎一些事,顺其自然以最佳心态面对,因为这个世界就是这样:往往在最在乎的事物面前,我们最没有价值。

经典语录21: 

一个人的成就,不是以金钱衡量,而是一生中,你善待过多少人,有多少人怀念你。生意人的账簿,记录收入与支出,两数相减,便是盈利。人生的账簿,记录爱与被爱,两数相加,就是成就。
posted @ 2011-05-09 17:57 小果子 阅读(169) | 评论 (0)编辑 收藏

引言

本书主要针对的是 C++ 程序的性能优化,深入介绍 C++ 程序性能优化的方法和实例。全书由 4 个篇组成,第 1 篇介绍 C++ 语言的对象模型,该篇是优化 C++ 程序的基础;第 2 篇主要针对如何优化 C++ 程序的内存使用;第 3 篇介绍如何优化程序的启动性能;第 4 篇介绍了三类性能优化工具,即内存分析工具、性能分析工具和 I/O 检测工具,它们是测量程序性能的利器。

本章首先简单介绍自定义内存池性能优化的原理,然后列举软件开发中常用的内存池的不同类型,并给出具体实现的实例。

6.1 自定义内存池性能优化的原理

 

图书信息 书名:《C++应用程序性能优化》
作者:冯宏华、徐莹、程远、汪磊 等编著
出版社:电子工业出版社
出版日期:2007 年 03 月
ISBN:978-7-121-03831-0
购买: 中国互动出版网dearbook

推荐章节:

 

更多推荐书籍,请访问 developerWorks 图书频道

欢迎您对本书提出宝贵的反馈意见。您可以通过本页面最下方的 建议 栏目为本文打分,并反馈您的建议和意见。

如果您对 developerWorks 图书频道有什么好的建议,欢迎您将建议发给我们

 

如前所述,读者已经了解到"堆"和"栈"的区别。而在编程实践中,不可避免地要大量用到堆上的内存。例如在程序中维护一个链表的数据结构时,每次新增或者删除一个链表的节点,都需要从内存堆上分配或者释放一定的内存;在维护一个动态数组时,如果动态数组的大小不能满足程序需要时,也要在内存堆上分配新的内存空间。

6.1.1 默认内存管理函数的不足

利用默认的内存管理函数new/delete或malloc/free在堆上分配和释放内存会有一些额外的开销。

系统在接收到分配一定大小内存的请求时,首先查找内部维护的内存空闲块表,并且需要根据一定的算法(例如分配最先找到的不小于申请大小的内存块给请求者,或者分配最适于申请大小的内存块,或者分配最大空闲的内存块等)找到合适大小的空闲内存块。如果该空闲内存块过大,还需要切割成已分配的部分和较小的空闲块。然后系统更新内存空闲块表,完成一次内存分配。类似地,在释放内存时,系统把释放的内存块重新加入到空闲内存块表中。如果有可能的话,可以把相邻的空闲块合并成较大的空闲块。

默认的内存管理函数还考虑到多线程的应用,需要在每次分配和释放内存时加锁,同样增加了开销。

可见,如果应用程序频繁地在堆上分配和释放内存,则会导致性能的损失。并且会使系统中出现大量的内存碎片,降低内存的利用率。

默认的分配和释放内存算法自然也考虑了性能,然而这些内存管理算法的通用版本为了应付更复杂、更广泛的情况,需要做更多的额外工作。而对于某一个具体的应用程序来说,适合自身特定的内存分配释放模式的自定义内存池则可以获得更好的性能。

6.1.2 内存池的定义和分类

自定义内存池的思想通过这个"池"字表露无疑,应用程序可以通过系统的内存分配调用预先一次性申请适当大小的内存作为一个内存池,之后应用程序自己对内存的分配和释放则可以通过这个内存池来完成。只有当内存池大小需要动态扩展时,才需要再调用系统的内存分配函数,其他时间对内存的一切操作都在应用程序的掌控之中。

应用程序自定义的内存池根据不同的适用场景又有不同的类型。

从线程安全的角度来分,内存池可以分为单线程内存池和多线程内存池。单线程内存池整个生命周期只被一个线程使用,因而不需要考虑互斥访问的问题;多线程内存池有可能被多个线程共享,因此则需要在每次分配和释放内存时加锁。相对而言,单线程内存池性能更高,而多线程内存池适用范围更广。

从内存池可分配内存单元大小来分,可以分为固定内存池和可变内存池。所谓固定内存池是指应用程序每次从内存池中分配出来的内存单元大小事先已经确定,是固定不变的;而可变内存池则每次分配的内存单元大小可以按需变化,应用范围更广,而性能比固定内存池要低。

6.1.3 内存池工作原理示例

下面以固定内存池为例说明内存池的工作原理,如图6-1所示。


图6-1 固定内存池
图6-1  固定内存池

固定内存池由一系列固定大小的内存块组成,每一个内存块又包含了固定数量和大小的内存单元。

如图6-1所示,该内存池一共包含4个内存块。在内存池初次生成时,只向系统申请了一个内存块,返回的指针作为整个内存池的头指针。之后随着应用程序对内存的不断需求,内存池判断需要动态扩大时,才再次向系统申请新的内存块,并把所有这些内存块通过指针链接起来。对于操作系统来说,它已经为该应用程序分配了4个等大小的内存块。由于是大小固定的,所以分配的速度比较快;而对于应用程序来说,其内存池开辟了一定大小,内存池内部却还有剩余的空间。

例如放大来看第4个内存块,其中包含一部分内存池块头信息和3个大小相等的内存池单元。单元1和单元3是空闲的,单元2已经分配。当应用程序需要通过该内存池分配一个单元大小的内存时,只需要简单遍历所有的内存池块头信息,快速定位到还有空闲单元的那个内存池块。然后根据该块的块头信息直接定位到第1个空闲的单元地址,把这个地址返回,并且标记下一个空闲单元即可;当应用程序释放某一个内存池单元时,直接在对应的内存池块头信息中标记该内存单元为空闲单元即可。

可见与系统管理内存相比,内存池的操作非常迅速,它在性能优化方面的优点主要如下。

(1)针对特殊情况,例如需要频繁分配释放固定大小的内存对象时,不需要复杂的分配算法和多线程保护。也不需要维护内存空闲表的额外开销,从而获得较高的性能。

(2)由于开辟一定数量的连续内存空间作为内存池块,因而一定程度上提高了程序局部性,提升了程序性能。

(3)比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。

6.2 一个内存池的实现实例

本节分析在某个大型应用程序实际应用到的一个内存池实现,并详细讲解其使用方法与工作原理。这是一个应用于单线程环境且分配单元大小固定的内存池,一般用来为执行时会动态频繁地创建且可能会被多次创建的类对象或者结构体分配内存。

本节首先讲解该内存池的数据结构声明及图示,接着描述其原理及行为特征。然后逐一讲解实现细节,最后介绍如何在实际程序中应用此内存池,并与使用普通内存函数申请内存的程序性能作比较。

6.2.1 内部构造

内存池类MemoryPool的声明如下:


class MemoryPool
            {
            private:
            MemoryBlock*   pBlock;
            USHORT          nUnitSize;
            USHORT          nInitSize;
            USHORT          nGrowSize;
            public:
            MemoryPool( USHORT nUnitSize,
            USHORT nInitSize = 1024,
            USHORT nGrowSize = 256 );
            ~MemoryPool();
            void*           Alloc();
            void            Free( void* p );
            };
            

MemoryBlock为内存池中附着在真正用来为内存请求分配内存的内存块头部的结构体,它描述了与之联系的内存块的使用信息:


struct MemoryBlock
            {
            USHORT          nSize;
            USHORT          nFree;
            USHORT          nFirst;
            USHORT          nDummyAlign1;
            MemoryBlock*  pNext;
            char            aData[1];
            static void* operator new(size_t, USHORT nTypes, USHORT nUnitSize)
            {
            return ::operator new(sizeof(MemoryBlock) + nTypes * nUnitSize);
            }
            static void  operator delete(void *p, size_t)
            {
            ::operator delete (p);
            }
            MemoryBlock (USHORT nTypes = 1, USHORT nUnitSize = 0);
            ~MemoryBlock() {}
            };
            

此内存池的数据结构如图6-2所示。


图6-2 内存池的数据结构
图6-2  内存池的数据结构

6.2.2 总体机制

此内存池的总体机制如下。

(1)在运行过程中,MemoryPool内存池可能会有多个用来满足内存申请请求的内存块,这些内存块是从进程堆中开辟的一个较大的连续内存区域,它由一个MemoryBlock结构体和多个可供分配的内存单元组成,所有内存块组成了一个内存块链表,MemoryPool 的pBlock是这个链表的头。对每个内存块,都可以通过其头部的MemoryBlock结构体的pNext成员访问紧跟在其后面的那个内存块。

(2)每个内存块由两部分组成,即一个MemoryBlock结构体和多个内存分配单元。这些内存分配单元大小固定(由 MemoryPool的nUnitSize表示),MemoryBlock结构体并不维护那些已经分配的单元的信息;相反,它只维护没有分配的自由分配单元的信息。它有两个成员比较重要:nFree和nFirst。nFree记录这个内存块中还有多少个自由分配单元,而nFirst则记录下一个可供分配的单元的编号。每一个自由分配单元的头两个字节(即一个USHORT型值)记录了紧跟它之后的下一个自由分配单元的编号,这样,通过利用每个自由分配单元的头两个字节,一个MemoryBlock中的所有自由分配单元被链接起来。

(3)当有新的内存请求到来时,MemoryPool会通过pBlock遍历MemoryBlock链表,直到找到某个 MemoryBlock所在的内存块,其中还有自由分配单元(通过检测MemoryBlock结构体的nFree成员是否大于0)。如果找到这样的内存块,取得其MemoryBlock的nFirst值(此为该内存块中第1个可供分配的自由单元的编号)。然后根据这个编号定位到该自由分配单元的起始位置(因为所有分配单元大小固定,因此每个分配单元的起始位置都可以通过编号分配单元大小来偏移定位),这个位置就是用来满足此次内存申请请求的内存的起始地址。但在返回这个地址前,需要首先将该位置开始的头两个字节的值(这两个字节值记录其之后的下一个自由分配单元的编号)赋给本内存块的 MemoryBlock的nFirst成员。这样下一次的请求就会用这个编号对应的内存单元来满足,同时将此内存块的MemoryBlock的nFree 递减1,然后才将刚才定位到的内存单元的起始位置作为此次内存请求的返回地址返回给调用者。

(4)如果从现有的内存块中找不到一个自由的内存分配单元(当第1次请求内存,以及现有的所有内存块中的所有内存分配单元都已经被分配时会发生这种情形),MemoryPool就会从进程堆中申请一个内存块(这个内存块包括一个MemoryBlock结构体,及紧邻其后的多个内存分配单元,假设内存分配单元的个数为n,n可以取值MemoryPool中的nInitSize或者nGrowSize),申请完后,并不会立刻将其中的一个分配单元分配出去,而是需要首先初始化这个内存块。初始化的操作包括设置MemoryBlock的nSize为所有内存分配单元的大小(注意,并不包括MemoryBlock结构体的大小)、nFree为n-1(注意,这里是n-1而不是n,因为此次新内存块就是为了满足一次新的内存请求而申请的,马上就会分配一块自由存储单元出去,如果设为n-1,分配一个自由存储单元后无须再将n递减1),nFirst为1(已经知道nFirst为下一个可以分配的自由存储单元的编号。为1的原因与nFree为n-1相同,即立即会将编号为0的自由分配单元分配出去。现在设为1,其后不用修改nFirst的值),MemoryBlock的构造需要做更重要的事情,即将编号为0的分配单元之后的所有自由分配单元链接起来。如前所述,每个自由分配单元的头两个字节用来存储下一个自由分配单元的编号。另外,因为每个分配单元大小固定,所以可以通过其编号和单元大小(MemoryPool的nUnitSize成员)的乘积作为偏移值进行定位。现在唯一的问题是定位从哪个地址开始?答案是MemoryBlock的aData[1]成员开始。因为aData[1]实际上是属于MemoryBlock结构体的(MemoryBlock结构体的最后一个字节),所以实质上,MemoryBlock结构体的最后一个字节也用做被分配出去的分配单元的一部分。因为整个内存块由MemoryBlock结构体和整数个分配单元组成,这意味着内存块的最后一个字节会被浪费,这个字节在图6-2中用位于两个内存的最后部分的浓黑背景的小块标识。确定了分配单元的起始位置后,将自由分配单元链接起来的工作就很容易了。即从aData位置开始,每隔nUnitSize大小取其头两个字节,记录其之后的自由分配单元的编号。因为刚开始所有分配单元都是自由的,所以这个编号就是自身编号加1,即位置上紧跟其后的单元的编号。初始化后,将此内存块的第1个分配单元的起始地址返回,已经知道这个地址就是aData。

(5)当某个被分配的单元因为delete需要回收时,该单元并不会返回给进程堆,而是返回给MemoryPool。返回时,MemoryPool能够知道该单元的起始地址。这时,MemoryPool开始遍历其所维护的内存块链表,判断该单元的起始地址是否落在某个内存块的地址范围内。如果不在所有内存地址范围内,则这个被回收的单元不属于这个MemoryPool;如果在某个内存块的地址范围内,那么它会将这个刚刚回收的分配单元加到这个内存块的MemoryBlock所维护的自由分配单元链表的头部,同时将其nFree值递增1。回收后,考虑到资源的有效利用及后续操作的性能,内存池的操作会继续判断:如果此内存块的所有分配单元都是自由的,那么这个内存块就会从MemoryPool中被移出并作为一个整体返回给进程堆;如果该内存块中还有非自由分配单元,这时不能将此内存块返回给进程堆。但是因为刚刚有一个分配单元返回给了这个内存块,即这个内存块有自由分配单元可供下次分配,因此它会被移到MemoryPool维护的内存块的头部。这样下次的内存请求到来,MemoryPool遍历其内存块链表以寻找自由分配单元时,第1次寻找就会找到这个内存块。因为这个内存块确实有自由分配单元,这样可以减少MemoryPool的遍历次数。

综上所述,每个内存池(MemoryPool)维护一个内存块链表(单链表),每个内存块由一个维护该内存块信息的块头结构(MemoryBlock)和多个分配单元组成,块头结构MemoryBlock则进一步维护一个该内存块的所有自由分配单元组成的"链表"。这个链表不是通过"指向下一个自由分配单元的指针"链接起来的,而是通过"下一个自由分配单元的编号"链接起来,这个编号值存储在该自由分配单元的头两个字节中。另外,第1个自由分配单元的起始位置并不是MemoryBlock结构体"后面的"第1个地址位置,而是MemoryBlock结构体"内部"的最后一个字节aData(也可能不是最后一个,因为考虑到字节对齐的问题),即分配单元实际上往前面错了一位。又因为MemoryBlock结构体后面的空间刚好是分配单元的整数倍,这样依次错位下去,内存块的最后一个字节实际没有被利用。这么做的一个原因也是考虑到不同平台的移植问题,因为不同平台的对齐方式可能不尽相同。即当申请MemoryBlock大小内存时,可能会返回比其所有成员大小总和还要大一些的内存。最后的几个字节是为了"补齐",而使得 aData成为第1个分配单元的起始位置,这样在对齐方式不同的各种平台上都可以工作。

6.2.3 细节剖析

有了上述的总体印象后,本节来仔细剖析其实现细节。

(1)MemoryPool的构造如下:


MemoryPool::MemoryPool( USHORT _nUnitSize,
            USHORT _nInitSize, USHORT _nGrowSize )
            {
            pBlock      = NULL;	            ①
            nInitSize   = _nInitSize;       ②
            nGrowSize   = _nGrowSize;       ③
            if ( _nUnitSize > 4 )
            nUnitSize = (_nUnitSize + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1); ④
            else if ( _nUnitSize <= 2 )
            nUnitSize = 2;              ⑤
            else
            nUnitSize = 4;
            }
            

从①处可以看出,MemoryPool创建时,并没有立刻创建真正用来满足内存申请的内存块,即内存块链表刚开始时为空。

②处和③处分别设置"第1次创建的内存块所包含的分配单元的个数",及"随后创建的内存块所包含的分配单元的个数",这两个值在MemoryPool创建时通过参数指定,其后在该MemoryPool对象生命周期中一直不变。

后面的代码用来设置nUnitSize,这个值参考传入的_nUnitSize参数。但是还需要考虑两个因素。如前所述,每个分配单元在自由状态时,其头两个字节用来存放"其下一个自由分配单元的编号"。即每个分配单元"最少"有"两个字节",这就是⑤处赋值的原因。④处是将大于4个字节的大小_nUnitSize往上"取整到"大于_nUnitSize的最小的MEMPOOL_ ALIGNMENT的倍数(前提是MEMPOOL_ALIGNMENT为2的倍数)。如_nUnitSize为11 时,MEMPOOL_ALIGNMENT为8,nUnitSize为16;MEMPOOL_ALIGNMENT为4,nUnitSize为 12;MEMPOOL_ALIGNMENT为2,nUnitSize为12,依次类推。

(2)当向MemoryPool提出内存请求时:


void* MemoryPool::Alloc()
            {
            if ( !pBlock )           ①
            {
            ……
            }
            MemoryBlock* pMyBlock = pBlock;
            while (pMyBlock && !pMyBlock->nFree )②
            pMyBlock = pMyBlock->pNext;
            if ( pMyBlock )	         ③
            {
            char* pFree = pMyBlock->aData+(pMyBlock->nFirst*nUnitSize);
            pMyBlock->nFirst = *((USHORT*)pFree);
            pMyBlock->nFree--;
            return (void*)pFree;
            }
            else                    ④
            {
            if ( !nGrowSize )
            return NULL;
            pMyBlock = new(nGrowSize, nUnitSize) FixedMemBlock(nGrowSize, nUnitSize);
            if ( !pMyBlock )
            return NULL;
            pMyBlock->pNext = pBlock;
            pBlock = pMyBlock;
            return (void*)(pMyBlock->aData);
            }
            }
            

MemoryPool满足内存请求的步骤主要由四步组成。

①处首先判断内存池当前内存块链表是否为空,如果为空,则意味着这是第1次内存申请请求。这时,从进程堆中申请一个分配单元个数为nInitSize的内存块,并初始化该内存块(主要初始化MemoryBlock结构体成员,以及创建初始的自由分配单元链表,下面会详细分析其代码)。如果该内存块申请成功,并初始化完毕,返回第1个分配单元给调用函数。第1个分配单元以MemoryBlock结构体内的最后一个字节为起始地址。

②处的作用是当内存池中已有内存块(即内存块链表不为空)时遍历该内存块链表,寻找还有"自由分配单元"的内存块。

③处检查如果找到还有自由分配单元的内存块,则"定位"到该内存块现在可以用的自由分配单元处。"定位"以 MemoryBlock结构体内的最后一个字节位置aData为起始位置,以MemoryPool的nUnitSize为步长来进行。找到后,需要修改 MemoryBlock的nFree信息(剩下来的自由分配单元比原来减少了一个),以及修改此内存块的自由存储单元链表的信息。在找到的内存块中,pMyBlock->nFirst为该内存块中自由存储单元链表的表头,其下一个自由存储单元的编号存放在 pMyBlock->nFirst指示的自由存储单元(亦即刚才定位到的自由存储单元)的头两个字节。通过刚才定位到的位置,取其头两个字节的值,赋给pMyBlock->nFirst,这就是此内存块的自由存储单元链表的新的表头,即下一次分配出去的自由分配单元的编号(如果nFree大于零的话)。修改维护信息后,就可以将刚才定位到的自由分配单元的地址返回给此次申请的调用函数。注意,因为这个分配单元已经被分配,而内存块无须维护已分配的分配单元,因此该分配单元的头两个字节的信息已经没有用处。换个角度看,这个自由分配单元返回给调用函数后,调用函数如何处置这块内存,内存池无从知晓,也无须知晓。此分配单元在返回给调用函数时,其内容对于调用函数来说是无意义的。因此几乎可以肯定调用函数在用这个单元的内存时会覆盖其原来的内容,即头两个字节的内容也会被抹去。因此每个存储单元并没有因为需要链接而引入多余的维护信息,而是直接利用单元内的头两个字节,当其分配后,头两个字节也可以被调用函数利用。而在自由状态时,则用来存放维护信息,即下一个自由分配单元的编号,这是一个有效利用内存的好例子。

④处表示在②处遍历时,没有找到还有自由分配单元的内存块,这时,需要重新向进程堆申请一个内存块。因为不是第一次申请内存块,所以申请的内存块包含的分配单元个数为nGrowSize,而不再是nInitSize。与①处相同,先做这个新申请内存块的初始化工作,然后将此内存块插入MemoryPool的内存块链表的头部,再将此内存块的第1个分配单元返回给调用函数。将此新内存块插入内存块链表的头部的原因是该内存块还有很多可供分配的自由分配单元(除非nGrowSize等于1,这应该不太可能。因为内存池的含义就是一次性地从进程堆中申请一大块内存,以供后续的多次申请),放在头部可以使得在下次收到内存申请时,减少②处对内存块的遍历时间。

可以用图6-2的MemoryPool来展示MemoryPool::Alloc的过程。图6-3是某个时刻MemoryPool的内部状态。


图6-3 某个时刻MemoryPool的内部状态
图6-3  某个时刻MemoryPool的内部状态

因为MemoryPool的内存块链表不为空,因此会遍历其内存块链表。又因为第1个内存块里有自由的分配单元,所以会从第1个内存块中分配。检查nFirst,其值为m,这时pBlock->aData+(pBlock->nFirst*nUnitSize) 定位到编号为m的自由分配单元的起始位置(用pFree表示)。在返回pFree之前,需要修改此内存块的维护信息。首先将nFree递减1,然后取得 pFree处开始的头两个字节的值(需要说明的是,这里aData处值为k。其实不是这一个字节。而是以aData和紧跟其后的另外一个字节合在一起构成的一个USHORT的值,不可误会)。发现为k,这时修改pBlock的nFirst为k。然后,返回pFree。此时MemoryPool的结构如图 6-4所示。


图6-4 MemoryPool的结构
图6-4  MemoryPool的结构

可以看到,原来的第1个可供分配的单元(m编号处)已经显示为被分配的状态。而pBlock的nFirst已经指向原来m单元下一个自由分配单元的编号,即k。

(3)MemoryPool回收内存时:


void MemoryPool::Free( void* pFree )
            {
            ……
            MemoryBlock* pMyBlock = pBlock;
            while ( ((ULONG)pMyBlock->aData > (ULONG)pFree) ||
            ((ULONG)pFree >= ((ULONG)pMyBlock->aData + pMyBlock->nSize)) )①
            {
            ……
            }
            pMyBlock->nFree++;                     ②
            *((USHORT*)pFree) = pMyBlock->nFirst;  ③
            pMyBlock->nFirst = (USHORT)(((ULONG)pFree-(ULONG)(pBlock->aData)) / nUnitSize);④
            if (pMyBlock->nFree*nUnitSize == pMyBlock->nSize )⑤
            {
            ……
            }
            else
            {
            ……
            }
            }
            

如前所述,回收分配单元时,可能会将整个内存块返回给进程堆,也可能将被回收分配单元所属的内存块移至内存池的内存块链表的头部。这两个操作都需要修改链表结构。这时需要知道该内存块在链表中前一个位置的内存块。

①处遍历内存池的内存块链表,确定该待回收分配单元(pFree)落在哪一个内存块的指针范围内,通过比较指针值来确定。

运行到②处,pMyBlock即找到的包含pFree所指向的待回收分配单元的内存块(当然,这时应该还需要检查 pMyBlock为NULL时的情形,即pFree不属于此内存池的范围,因此不能返回给此内存池,读者可以自行加上)。这时将pMyBlock的 nFree递增1,表示此内存块的自由分配单元多了一个。

③处用来修改该内存块的自由分配单元链表的信息,它将这个待回收分配单元的头两个字节的值指向该内存块原来的第一个可分配的自由分配单元的编号。

④处将pMyBlock的nFirst值改变为指向这个待回收分配单元的编号,其编号通过计算此单元的起始位置相对pMyBlock的aData位置的差值,然后除以步长(nUnitSize)得到。

实质上,③和④两步的作用就是将此待回收分配单元"真正回收"。值得注意的是,这两步实际上是使得此回收单元成为此内存块的下一个可分配的自由分配单元,即将它放在了自由分配单元链表的头部。注意,其内存地址并没有发生改变。实际上,一个分配单元的内存地址无论是在分配后,还是处于自由状态时,一直都不会变化。变化的只是其状态(已分配/自由),以及当其处于自由状态时在自由分配单元链表中的位置。

⑤处检查当回收完毕后,包含此回收单元的内存块的所有单元是否都处于自由状态,且此内存是否处于内存块链表的头部。如果是,将此内存块整个的返回给进程堆,同时修改内存块链表结构。

注意,这里在判断一个内存块的所有单元是否都处于自由状态时,并没有遍历其所有单元,而是判断nFree乘以 nUnitSize是否等于nSize。nSize是内存块中所有分配单元的大小,而不包括头部MemoryBlock结构体的大小。这里可以看到其用意,即用来快速检查某个内存块中所有分配单元是否全部处于自由状态。因为只需结合nFree和nUnitSize来计算得出结论,而无须遍历和计算所有自由状态的分配单元的个数。

另外还需注意的是,这里并不能比较nFree与nInitSize或nGrowSize的大小来判断某个内存块中所有分配单元都为自由状态,这是因为第1次分配的内存块(分配单元个数为nInitSize)可能被移到链表的后面,甚至可能在移到链表后面后,因为某个时间其所有单元都处于自由状态而被整个返回给进程堆。即在回收分配单元时,无法判定某个内存块中的分配单元个数到底是nInitSize还是nGrowSize,也就无法通过比较nFree与nInitSize或nGrowSize的大小来判断一个内存块的所有分配单元是否都为自由状态。

以上面分配后的内存池状态作为例子,假设这时第2个内存块中的最后一个单元需要回收(已被分配,假设其编号为m,pFree指针指向它),如图6-5所示。

不难发现,这时nFirst的值由原来的0变为m。即此内存块下一个被分配的单元是m编号的单元,而不是0编号的单元(最先分配的是最新回收的单元,从这一点看,这个过程与栈的原理类似,即先进后出。只不过这里的"进"意味着"回收",而"出"则意味着"分配")。相应地,m的"下一个自由单元"标记为0,即内存块原来的"下一个将被分配出去的单元",这也表明最近回收的分配单元被插到了内存块的"自由分配单元链表"的头部。当然,nFree递增1。


图6-5 分配后的内存池状态
图6-5  分配后的内存池状态

处理至⑥处之前,其状态如图6-6所示。


图6-6 处理至⑥处之前的内存池状态
图6-6  处理至⑥处之前的内存池状态

这里需要注意的是,虽然pFree被"回收",但是pFree仍然指向m编号的单元,这个单元在回收过程中,其头两个字节被覆写,但其他部分的内容并没有改变。而且从整个进程的内存使用角度来看,这个m编号的单元的状态仍然是"有效的"。因为这里的"回收"只是回收给了内存池,而并没有回收给进程堆,因此程序仍然可以通过pFree访问此单元。但是这是一个很危险的操作,因为首先该单元在回收过程中头两个字节已被覆写,并且该单元可能很快就会被内存池重新分配。因此回收后通过pFree指针对这个单元的访问都是错误的,读操作会读到错误的数据,写操作则可能会破坏程序中其他地方的数据,因此需要格外小心。

接着,需要判断该内存块的内部使用情况,及其在内存块链表中的位置。如果该内存块中省略号"……"所表示的其他部分中还有被分配的单元,即nFree乘以nUnitSize不等于nSize。因为此内存块不在链表头,因此还需要将其移到链表头部,如图6-7所示。


图6-7 因回收引起的MemoryBlock移动
图6-7  因回收引起的MemoryBlock移动

如果该内存块中省略号"……"表示的其他部分中全部都是自由分配单元,即nFree乘以nUnitSize等于nSize。因为此内存块不在链表头,所以此时需要将此内存块整个回收给进程堆,回收后内存池的结构如图6-8所示。


图6-8 回收后内存池的结构
图6-8  回收后内存池的结构

一个内存块在申请后会初始化,主要是为了建立最初的自由分配单元链表,下面是其详细代码:


MemoryBlock::MemoryBlock (USHORT nTypes, USHORT nUnitSize)
            : nSize  (nTypes * nUnitSize),
            nFree  (nTypes - 1),                     ④
            nFirst (1),                              ⑤
            pNext  (0)
            {
            char * pData = aData;                  ①
            for (USHORT i = 1; i < nTypes; i++) ②
            {
            *reinterpret_cast<USHORT*>(pData) = i; ③
            pData += nUnitSize;
            }
            }
            

这里可以看到,①处pData的初值是aData,即0编号单元。但是②处的循环中i却是从1开始,然后在循环内部的③处将pData的头两个字节值置为i。即0号单元的头两个字节值为1,1号单元的头两个字节值为2,一直到(nTypes-2)号单元的头两个字节值为(nTypes-1)。这意味着内存块初始时,其自由分配单元链表是从0号开始。依次串联,一直到倒数第2个单元指向最后一个单元。

还需要注意的是,在其初始化列表中,nFree初始化为nTypes-1(而不是nTypes),nFirst初始化为 1(而不是0)。这是因为第1个单元,即0编号单元构造完毕后,立刻会被分配。另外注意到最后一个单元初始并没有设置头两个字节的值,因为该单元初始在本内存块中并没有下一个自由分配单元。但是从上面例子中可以看到,当最后一个单元被分配并回收后,其头两个字节会被设置。

图6-9所示为一个内存块初始化后的状态。


图6-9 一个内存块初始化后的状态
图6-9  一个内存块初始化后的状态

当内存池析构时,需要将内存池的所有内存块返回给进程堆:


MemoryPool::~MemoryPool()
            {
            MemoryBlock* pMyBlock = pBlock;
            while ( pMyBlock )
            {
            ……
            }
            }
            

6.2.4 使用方法

分析内存池的内部原理后,本节说明如何使用它。从上面的分析可以看到,该内存池主要有两个对外接口函数,即Alloc和 Free。Alloc返回所申请的分配单元(固定大小内存),Free则回收传入的指针代表的分配单元的内存给内存池。分配的信息则通过 MemoryPool的构造函数指定,包括分配单元大小、内存池第1次申请的内存块中所含分配单元的个数,以及内存池后续申请的内存块所含分配单元的个数等。

综上所述,当需要提高某些关键类对象的申请/回收效率时,可以考虑将该类所有生成对象所需的空间都从某个这样的内存池中开辟。在销毁对象时,只需要返回给该内存池。"一个类的所有对象都分配在同一个内存池对象中"这一需求很自然的设计方法就是为这样的类声明一个静态内存池对象,同时为了让其所有对象都从这个内存池中开辟内存,而不是缺省的从进程堆中获得,需要为该类重载一个new运算符。因为相应地,回收也是面向内存池,而不是进程的缺省堆,还需要重载一个delete运算符。在new运算符中用内存池的Alloc函数满足所有该类对象的内存请求,而销毁某对象则可以通过在 delete运算符中调用内存池的Free完成。

6.2.5 性能比较

为了测试利用内存池后的效果,通过一个很小的测试程序可以发现采用内存池机制后耗时为297 ms。而没有采用内存池机制则耗时625 ms,速度提高了52.48%。速度提高的原因可以归结为几点,其一,除了偶尔的内存申请和销毁会导致从进程堆中分配和销毁内存块外,绝大多数的内存申请和销毁都由内存池在已经申请到的内存块中进行,而没有直接与进程堆打交道,而直接与进程堆打交道是很耗时的操作;其二,这是单线程环境的内存池,可以看到内存池的Alloc和Free操作中并没有加线程保护措施。因此如果类A用到该内存池,则所有类A对象的创建和销毁都必须发生在同一个线程中。但如果类A 用到内存池,类B也用到内存池,那么类A的使用线程可以不必与类B的使用线程是同一个线程。

另外,在第1章中已经讨论过,因为内存池技术使得同类型的对象分布在相邻的内存区域,而程序会经常对同一类型的对象进行遍历操作。因此在程序运行过程中发生的缺页应该会相应少一些,但这个一般只能在真实的复杂应用环境中进行验证。

6.3 本章小结

内存的申请和释放对一个应用程序的整体性能影响极大,甚至在很多时候成为某个应用程序的瓶颈。消除内存申请和释放引起的瓶颈的方法往往是针对内存使用的实际情况提供一个合适的内存池。内存池之所以能够提高性能,主要是因为它能够利用应用程序的实际内存使用场景中的某些"特性"。比如某些内存申请与释放肯定发生在一个线程中,某种类型的对象生成和销毁与应用程序中的其他类型对象要频繁得多,等等。针对这些特性,可以为这些特殊的内存使用场景提供量身定做的内存池。这样能够消除系统提供的缺省内存机制中,对于该实际应用场景中的不必要的操作,从而提升应用程序的整体性能。

posted @ 2011-04-05 20:18 小果子 阅读(2039) | 评论 (0)编辑 收藏

 内存泄露相信对C++程序员来说都不陌生。解决内存泄露的方案多种多样,大部分方案以追踪检测为主,这种方法实现起来容易,使用方便,也比较安全。

         首先我们要确定这个模块的主要功能:

  1. 能追踪内存的分配和释放过程。
  2. 要能显示内存分配的相关信息,比如内存块大小,代码所在文件所在行等。
  3. 在发现内存泄露时及时给出相关信息。
  4. 能正确处理一些异常情况,比如内存不足,对象初始化失败等等。
  5. 是线程安全的。[*这个还没有实现]

        有了一些基本功能需求,我们需要考虑每种功能怎么去实现。首先,我们可以通过重载的方式来追踪new,delete.malloc和free,C++给我提供了这样的特性。因为本文主要针对C++,所以主要讲重载new,delete的方法,malloc和free的重载实现于此类似,最终版本的程序中也实现了malloc和free的重载。

1.重载new和delete

        首先我们要了解一下new和delete是怎么工作的。C++中的操作符最终都会被转换成函数形式,例如"new int"会变成"opetaor new(sizeof(int))",而"new double[10]"会变成"operator new(sizeof(double)*10)",同样“delete p”就变成了"operator delete(p)"。另外一个需要特别注意的地方是,new对于用户定义的数据类型(即你的自定义类)会自动调用该类型的构造函数,如果构造函数没有抛出异常,则正确分配,否则会中断分配操作,将异常传递给用户。默认情况下,new可以对象构造异常进行捕获。另外一个版本的new就是不带捕获异常功能的的了,所以C++系统提供的new和delete有:

1
2
3
4
5
6
7
8
9
void* operator new(size_t size)throw(std::bad_alloc);
void* operator new[](size_t size) throw(std::bad_alloc);
void* operator new(size_t,std::nothrow_t&)throw();
void* operator new[](size_t,std::nothrow_t&)throw();
 
void  operator delete(void* pointer);
void  operator delete[](void* pointer);
void  operator delete(void* pointer,std::nothrow_t&);
void  operator delete[](void* pointer,std::nothrow_t&);<br>

        其中,nothrow_t是一个空结构体“struct nothrow_t{}",它的一个实例就是nothrow,C++用它来区分可以捕获异常的new和不可捕获异常的new。我们不能直接修改内部函数的行为,但是我们可以重载它们。为了实现提供内存分配信息的功能,我们给重载的函数加上几个参数。得到以下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void* operator new(size_t size);
void* operator new[](size_t size);
void* operator new(size_t,std::nothrow_t&)throw();
void* operator new[](size_t,std::nothrow_t&)throw();
void* operator new(size_t size,const char* file,const char* func,const int line)throw(std::bad_alloc);
void* operator new[](size_t size,const char* file,const char* func,const int line) throw(std::bad_alloc);
void* operator delete(void* pointer);
void* operator delete[](void* pointer);
/*******Placement Delete********/
void  operator delete(void* pointer,const char* file,const char* func,const int line);
void  operator delete[](void* pointer,const char* file,const char* func,const int line);
void  operator delete(void* pointer,std::nothrow_t&);
void  operator delete[](void* pointer,std::nothrow_t&);
/*******************************/

         中间的几个函数,就是我们主要需要重载的函数,模块的大部分工作也都由着几个函数完成。这些函数参数中,file表示分配代码所在的文件名,func表示代码所在的函数名,line表示代码行号。这几个参数信息我们可以通过编译器预定义好的几个宏来获得:__FILE__,__FUNCTION__,__LINE__。也就是说可以将"new ..."替换成"new(__FILE__,__FUNCTION__,__LINE__) ...",最终成为"operator new(sizeof(...),__FILE__,__FUNCTION__,__LINE__)"的形式,也就达到了我们的目的。关于 placement delete将在下面详细解释。

2.空间分配

        接下来我们要考虑内存分配信息的组织问题了。我们先来了解一下编译器是怎么组织的。在大部分编译器中,new所分配的空间都要大于实际申请的空间,大出来的部分就是编译器定义的内存块的信息,包括了内存块的大小还有一些其他信息。如下图所示:

        我们把包含内存分配信息的部分叫做cookie数据。为了方便,我们把cookie数据放在分配的内存的起始位置,之后紧接有效数据区。我们还需要把返回给调用者的指针和new分配的数据区联系起来,原本想用性能比较好的STL的map数据结构来储存这些数据,但是map内部同样也使用new来分配内存,所以如果直接使用map来储存,就会陷入死循环中。所以这里我们必须自己现实一个数据结构。我们可以对返回给调用者的地址进行Hash,得到hash 表中的地址,具有相同Hash值的数据我们用一个单向链表连接起来。最终的数据结构如下图所示:

2.1.构造函数中的异常

        另外一个必须要注意的一点是,new操作符会先分配空间然后调用用户自定义类型的构造函数,如果构造函数抛出异常,需要用户手动释放已分配的内存。问题在于释放这样的内存不能用一般的delete操作符,可以用一个例子来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdexcept>
 
void* operator new(size_t size, int line) {
    printf("Allocate %u bytes on line %d\\n", size, line);
    return operator new(size);
}
 
class UserClass {
public:
    UserClass(int n)throw(){
        if(n<=0){
             throw std::runtime_error("n must be positive");
        }
    }
};
 
int main(){
    try{
        UserClass* myobj=new(__LINE__) UserClass(-10);
        delete myobj; //doesn't work if placement was not defined
    } catch(const std::runtime_error& e) {
        fprintf(stderr,"Exception: %s\n",e.what());
    }
    return 0;
}<br>

        这里,虽然在new过后试图使用delete释放已经分配的内存,但是实际上不会释放。也许你的编译器会给出这样一条消息:

        “no matching operator delete found”

        为了正确处理这种情况,并给用户提供相关的信息,我们需要定义placement delete操作符。placement delete是C++98标准中才有的一个特性,所以对于某些老的编译器(大致可以认为是指那些98年以前编写的编译器)不支持这个特性。这需要在模块中添加宏定义让用户可以关闭placement delete的定义,以便模块能在较老的编译器上编译。以下就是需要定义的placement delete操作符:

1
2
3
4
void  operator delete(void* pointer,const char* file,const char* func,const int line);
void  operator delete[](void* pointer,const char* file,const char* func,const int line);
void  operator delete(void* pointer,std::nothrow_t&);
void  operator delete[](void* pointer,std::nothrow_t&);<br>

3.检查内存泄露

        有了上面的实现,我们可以方便的手动检测内存泄露。通过一个函数来实现,它会检索整个hash表,如果表不为空则存在内存泄露。

        为了达到在最后程序退出时检查内存泄露的目的,我们需要在所有对象调用析构函数后进行内存泄露检测,这是因为某些用户类型在构造函数里调用new而在析构函数里调用delete。这样做能大大的减小误报的概率。而且因为对象的析构函数的调用往往在主函数main()执行结束之后进行,所以我们也不能直接在主函数里进行内存泄露检测。这里我们利用一个全局对象来实现这种检测。首先我们定义一个类:

1
2
3
4
5
6
7
8
9
10
11
class MemCheck{
    public:
        MemCheck(){
            memset(pTable,0,sizeof(mc_block_node_t*) * MC_HASHTABLESIZE);
        }
        ~MemCheck(){
            if(mc_checkmem()){
                abort();
            }
        }
};

        这里的构造函数初始化Hash表。析构函数检测内存泄露。然后定义一个MemCheck的全局静态对象,这样当程序运行之前会初始化hash表,程序退出时检测内存泄露。可是问题又来了,如果一个程序中有多个全局静态对象会怎样?不幸的是,对于全局静态对象的构造顺序和析构顺序是C++标准中的一个未定义问题,也就是说,这个顺序取决于编译器的具体实现。考虑,绝大多数平台使用VC和GCC编译器,我们可以针对这两种编译器来控制全局对象的构造和解析顺序。

1
2
3
4
5
6
7
8
9
#ifdef _MSC_VER
#pragma init_seg(lib)
#endif
 
static MemCheck mc_autocheck_object
#ifdef __GNUC__
__attribute__((init_priority (101)))
#endif
;

        这里的宏定义部分都是编译器的选项。

posted @ 2011-04-05 20:11 小果子 阅读(1598) | 评论 (1)编辑 收藏
提交服务器处理业务后结果返回页面的处理,Struts2提供了对不同种类返回结果的支持,常见的有JSP,FreeMarker,Velocity等。

struts.xml配置文件中result的语法:<result name="" type="">xxxxx</result>

Struts2支持的不同类型的返回结果为:

Chain Result-->type="chain"
用来处理Action链

Dispatcher Result -->type="dispatcher"
用来转向页面,通常处理JSP

FreeMarker Result -->type="freemarker"
处理FreeMarker模板

HttpHeader Result -->type="httpheader"
用来控制特殊的Http行为

Redirect Result -->type="redirect"
重定向到一个URL

Redirect Action Result -->type="redirectAction"
重定向到一个Action

Stream Result -->type="stream"
向浏览器发送InputSream对象,通常用来处理文件下载

Velocity Result -->type="velocity"
处理Velocity模板

XLST Result -->type="xslt"
处理XML/XLST模板

PlainText Result -->type="plainText"
显示原始文件内容,例如文件源代码


另外第三方的result类型还包括JasperReports Plugin,专门用来处理JasperReport类型的报表输出。

在struts-default.xml文件中已经有了对于所有类型Result的定义:

Java 代码

1. <result-types>  
2.  
3.     <result-type name="chain"  
4.  
5.              class="com.opensymphony.xwork2.ActionChainResult"/>  
6.  
7.     <result-type name="dispatcher"  
8.  
9.              class="org.apache.struts2.dispatcher.ServletDispatcherResult"  
10.  
11.              default="true"/>  
12.  
13.     <result-type name="freemarker"  
14.  
15.              class="org.apache.struts2.views.freemarker.FreemarkerResult"/>  
16.  
17.     <result-type name="httpheader"  
18.  
19.              class="org.apache.struts2.dispatcher.HttpHeaderResult"/>  
20.  
21.     <result-type name="redirect"  
22.  
23.              class="org.apache.struts2.dispatcher.ServletRedirectResult"/>  
24.  
25.     <result-type name="redirectAction"  
26.  
27.              class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>  
28.  
29.     <result-type name="stream"  
30.  
31.              class="org.apache.struts2.dispatcher.StreamResult"/>  
32.  
33.     <result-type name="velocity"  
34.  
35.              class="org.apache.struts2.dispatcher.VelocityResult"/>  
36.  
37.     <result-type name="xslt"  
38.  
39.              class="org.apache.struts2.views.xslt.XSLTResult"/>  
40.  
41.     <result-type name="plainText"  
42.  
43.              class="org.apache.struts2.dispatcher.PlainTextResult" />  
44.  
45.     <!-- Deprecated name form scheduled for removal in Struts 2.1.0.  
46.  
47.          The camelCase versions are preferred. See ww-1707 -->  
48.  
49.     <result-type name="redirect-action"  
50.  
51.              class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>  
52.  
53.     <result-type name="plaintext"  
54.  
55.              class="org.apache.struts2.dispatcher.PlainTextResult" />  
56.  
57. </result-types>  

<result-types>

<result-type name="chain"

class="com.opensymphony.xwork2.ActionChainResult"/>

<result-type name="dispatcher"

class="org.apache.struts2.dispatcher.ServletDispatcherResult"

default="true"/>

<result-type name="freemarker"

class="org.apache.struts2.views.freemarker.FreemarkerResult"/>

<result-type name="httpheader"

class="org.apache.struts2.dispatcher.HttpHeaderResult"/>

<result-type name="redirect"

class="org.apache.struts2.dispatcher.ServletRedirectResult"/>

<result-type name="redirectAction"

class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

<result-type name="stream"

class="org.apache.struts2.dispatcher.StreamResult"/>

<result-type name="velocity"

class="org.apache.struts2.dispatcher.VelocityResult"/>

<result-type name="xslt"

class="org.apache.struts2.views.xslt.XSLTResult"/>

<result-type name="plainText"

class="org.apache.struts2.dispatcher.PlainTextResult" />

<!-- Deprecated name form scheduled for removal in Struts 2.1.0.

The camelCase versions are preferred. See ww-1707 -->

<result-type name="redirect-action"

class="org.apache.struts2.dispatcher.ServletActionRedirectResult"/>

<result-type name="plaintext"

class="org.apache.struts2.dispatcher.PlainTextResult" />

</result-types>



从上述代码中可以看出在不指定Result类型的时候默认使用dispatcher类型。


定义一个Result值,

Java 代码

1. <result name="success" type="dispatcher">  
2.  
3.     <param name="location">/myjsp.jsp</param>  
4.  
5. </result>  

<result name="success" type="dispatcher">

<param name="location">/myjsp.jsp</param>

</result>


由于type默认值是dispatcher,所以这里不需要定义,另外name的默认值为success所以这里也不需要定义。
上述代码可以简写为:

Java 代码

1. <result>  
2.  
3.     <param name="location">/myjsp.jsp</param>  
4.  
5. </result>  

<result>

<param name="location">/myjsp.jsp</param>

</result>




另外location参数也可以直接卸载result标签内部(也就是无需再result里面使用),所以上述代码的最简单的写法为:

Java 代码

1. <result>/myjsp.jsp</result>  

<result>/myjsp.jsp</result>



我们也可以定义多个不同的result

Java 代码

1. <action name="Hello">  
2.  
3. <result>/hello/hello.jsp</result>  
4.  
5. <result name="error">/hello/error.jsp</result>  
6.  
7. <result name="input">/hello/input.jsp</result>  
8.  
9. </action>  

<action name="Hello">

<result>/hello/hello.jsp</result>

<result name="error">/hello/error.jsp</result>

<result name="input">/hello/input.jsp</result>

</action>



上 述代码的含义为,名字为Hello的Action有三个返回结果,并且都是 dispatcher类型(默认类型),这三个返回值的名字分别为success(默认值),error,input(当输入不通过时,action 方法返回input),对应的页面的路径分别为 /hello/result.jsp,/hello/error.jsp,/hello/input.jsp。

有些时候我们需要一个定义在全局的result,这个时候我们可以在package内部定义全局的result,例如:

Java 代码

1. <global-results>  
2.  
3. <result name="error">/error.jsp</result>  
4.  
5. <result name="invalid.token">/error.jsp</result>  
6.  
7. <result name="login" type="redirect-action">login!input</result>  
8.  
9. </global-results>  

<global-results>

<result name="error">/error.jsp</result>

<result name="invalid.token">/error.jsp</result>

<result name="login" type="redirect-action">login!input</result>

</global-results>



动态返回结果

有些时候,只有当Action执行完璧的时候我们才知道要返回哪个结果,这个时候我们可以在Action内部定义一个属性,这个属性用来存储 Action执行完璧之后的Result值,例如:

Java 代码

1. private String nextAction;  
2.  
3. public String getNextAction() {  
4.  
5.     return nextAction;  
6.  
7. }  

private String nextAction;

public String getNextAction() {

return nextAction;

}



在strutx.xml配置文件中,我们可以使用${nextAction}来引用到Action中的属性,通过${nextAction}表示的内容来动态的返回结果,例如:

Java 代码

1. <action name="fragment" class="FragmentAction">  
2.  
3. <result name="next" type="redirect-action">${nextAction}</result>  
4.  
5. </action>  

<action name="fragment" class="FragmentAction">

<result name="next" type="redirect-action">${nextAction}</result>

</action>



上述Action的execute方法返回next的时候,还需要根据nextAction的属性来判断具体定位到哪个Action。


在struts.xml配置文件中,我们可以使用method=""来设置调用类的哪个方法,这样就可以在一个JAVA类中使用不同的方法来实现不同的功能,就无需每个功能写一类了,例如:
Java 代码

1. <action name="fragment" class="cn.com.web.FragmentAction" method="add">  
2.       <result>/success.jsp</result>  
3. </action> 
posted @ 2011-04-05 18:17 小果子 阅读(507) | 评论 (0)编辑 收藏

在struts的配置文件中的形式为:

<constant name="struts.i18n.encoding" value="UTF-8" />

struts.action.extension
The URL extension to use to determine if the request is meant for a Struts action
用URL扩展名来确定是否这个请求是被用作Struts action,其实也就是设置 action的后缀,例如login.do的'do'字。

struts.configuration
The org.apache.struts2.config.Configuration implementation class
org.apache.struts2.config.Configuration接口名

struts.configuration.files
A list of configuration files automatically loaded by Struts
struts自动加载的一个配置文件列表

struts.configuration.xml.reload
Whether to reload the XML configuration or not
是否加载xml配置(true,false)

struts.continuations.package
The package containing actions that use Rife continuations
含有actions的完整连续的package名称

struts.custom.i18n.resources
Location of additional localization properties files to load
加载附加的国际化属性文件(不包含.properties后缀)

struts.custom.properties
Location of additional configuration properties files to load
加载附加的配置文件的位置


struts.devMode
Whether Struts is in development mode or not
是否为struts开发模式

struts.dispatcher.parametersWorkaround
Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic
(某些版本的weblogic专用)是否使用一个servlet请求参数工作区(PARAMETERSWORKAROUND)

struts.enable.DynamicMethodInvocation
Allows one to disable dynamic method invocation from the URL
允许动态方法调用

struts.freemarker.manager.classname
The org.apache.struts2.views.freemarker.FreemarkerManager implementation class
org.apache.struts2.views.freemarker.FreemarkerManager接口名

struts.i18n.encoding
The encoding to use for localization messages
国际化信息内码

struts.i18n.reload
Whether the localization messages should automatically be reloaded
是否国际化信息自动加载

struts.locale
The default locale for the Struts application
默认的国际化地区信息

struts.mapper.class
The org.apache.struts2.dispatcher.mapper.ActionMapper implementation class
org.apache.struts2.dispatcher.mapper.ActionMapper接口

struts.multipart.maxSize
The maximize size of a multipart request (file upload)
multipart请求信息的最大尺寸(文件上传用)

struts.multipart.parser
The org.apache.struts2.dispatcher.multipart.MultiPartRequest parser implementation for a multipart request (file upload)
专为multipart请求信息使用的 org.apache.struts2.dispatcher.multipart.MultiPartRequest解析器接口(文件上传用)


struts.multipart.saveDir
The directory to use for storing uploaded files
设置存储上传文件的目录夹

struts.objectFactory
The com.opensymphony.xwork2.ObjectFactory implementation class
com.opensymphony.xwork2.ObjectFactory接口(spring)

struts.objectFactory.spring.autoWire
Whether Spring should autoWire or not
是否自动绑定Spring

struts.objectFactory.spring.useClassCache
Whether Spring should use its class cache or not
是否spring应该使用自身的cache

struts.objectTypeDeterminer
The com.opensymphony.xwork2.util.ObjectTypeDeterminer implementation class
com.opensymphony.xwork2.util.ObjectTypeDeterminer接口

struts.serve.static.browserCache
If static content served by the Struts filter should set browser caching header properties or not
是否struts过滤器中提供的静态内容应该被浏览器缓存在头部属性中

struts.serve.static
Whether the Struts filter should serve static content or not
是否struts过滤器应该提供静态内容

struts.tag.altSyntax
Whether to use the alterative syntax for the tags or not
是否可以用替代的语法替代tags

struts.ui.templateDir
The directory containing UI templates
UI templates的目录夹

struts.ui.theme
The default UI template theme
默认的UI template主题

struts.url.http.port
The HTTP port used by Struts URLs
设置http端口

struts.url.https.port
The HTTPS port used by Struts URLs
设置https端口

struts.url.includeParams
The default includeParams method to generate Struts URLs
在url中产生 默认的includeParams


struts.velocity.configfile
The Velocity configuration file path
velocity配置文件路径

struts.velocity.contexts
List of Velocity context names
velocity的context列表


struts.velocity.manager.classname
org.apache.struts2.views.velocity.VelocityManager implementation class
org.apache.struts2.views.velocity.VelocityManager接口名

struts.velocity.toolboxlocation
The location of the Velocity toolbox
velocity工具盒的位置
struts.xslt.nocache
Whether or not XSLT templates should not be cached
是否XSLT模版应该被缓存

【原创】struts2的struts.properties配置文件详解

struts.action.extension
The URL extension to use to determine if the request is meant for a Struts action
用URL扩展名来确定是否这个请求是被用作Struts action,其实也就是设置 action的后缀,例如login.do的'do'字。

struts.configuration
The org.apache.struts2.config.Configuration implementation class
org.apache.struts2.config.Configuration接口名

struts.configuration.files
A list of configuration files automatically loaded by Struts
struts自动加载的一个配置文件列表

struts.configuration.xml.reload
Whether to reload the XML configuration or not
是否加载xml配置(true,false)

struts.continuations.package
The package containing actions that use Rife continuations
含有actions的完整连续的package名称

struts.custom.i18n.resources
Location of additional localization properties files to load
加载附加的国际化属性文件(不包含.properties后缀)

struts.custom.properties
Location of additional configuration properties files to load
加载附加的配置文件的位置


struts.devMode
Whether Struts is in development mode or not
是否为struts开发模式

struts.dispatcher.parametersWorkaround
Whether to use a Servlet request parameter workaround necessary for some versions of WebLogic
(某些版本的weblogic专用)是否使用一个servlet请求参数工作区(PARAMETERSWORKAROUND)

struts.enable.DynamicMethodInvocation
Allows one to disable dynamic method invocation from the URL
允许动态方法调用

struts.freemarker.manager.classname
The org.apache.struts2.views.freemarker.FreemarkerManager implementation class
org.apache.struts2.views.freemarker.FreemarkerManager接口名

struts.i18n.encoding
The encoding to use for localization messages
国际化信息内码

struts.i18n.reload
Whether the localization messages should automatically be reloaded
是否国际化信息自动加载

struts.locale
The default locale for the Struts application
默认的国际化地区信息

struts.mapper.class
The org.apache.struts2.dispatcher.mapper.ActionMapper implementation class
org.apache.struts2.dispatcher.mapper.ActionMapper接口

struts.multipart.maxSize
The maximize size of a multipart request (file upload)
multipart请求信息的最大尺寸(文件上传用)

struts.multipart.parser
The org.apache.struts2.dispatcher.multipart.MultiPartRequest parser implementation for a multipart request (file upload)
专为multipart请求信息使用的 org.apache.struts2.dispatcher.multipart.MultiPartRequest解析器接口(文件上传用)


struts.multipart.saveDir
The directory to use for storing uploaded files
设置存储上传文件的目录夹

struts.objectFactory
The com.opensymphony.xwork2.ObjectFactory implementation class
com.opensymphony.xwork2.ObjectFactory接口(spring)

struts.objectFactory.spring.autoWire
Whether Spring should autoWire or not
是否自动绑定Spring

struts.objectFactory.spring.useClassCache
Whether Spring should use its class cache or not
是否spring应该使用自身的cache

struts.objectTypeDeterminer
The com.opensymphony.xwork2.util.ObjectTypeDeterminer implementation class
com.opensymphony.xwork2.util.ObjectTypeDeterminer接口

struts.serve.static.browserCache
If static content served by the Struts filter should set browser caching header properties or not
是否struts过滤器中提供的静态内容应该被浏览器缓存在头部属性中

struts.serve.static
Whether the Struts filter should serve static content or not
是否struts过滤器应该提供静态内容

struts.tag.altSyntax
Whether to use the alterative syntax for the tags or not
是否可以用替代的语法替代tags

struts.ui.templateDir
The directory containing UI templates
UI templates的目录夹

struts.ui.theme
The default UI template theme
默认的UI template主题

struts.url.http.port
The HTTP port used by Struts URLs
设置http端口

struts.url.https.port
The HTTPS port used by Struts URLs
设置https端口

struts.url.includeParams
The default includeParams method to generate Struts URLs
在url中产生 默认的includeParams


struts.velocity.configfile
The Velocity configuration file path
velocity配置文件路径

struts.velocity.contexts
List of Velocity context names
velocity的context列表


struts.velocity.manager.classname
org.apache.struts2.views.velocity.VelocityManager implementation class
org.apache.struts2.views.velocity.VelocityManager接口名

struts.velocity.toolboxlocation
The location of the Velocity toolbox
velocity工具盒的位置
struts.xslt.nocache
Whether or not XSLT templates should not be cached
是否XSLT模版应该被缓存

struts2加载常量的顺序
struts-default.xml
struts-plugin.xml
struts.xml
struts.properties
web.xml
后面的会覆盖掉前面的常量,最好在struts.xml中定义

 

怎么由.action改为.do
<constant name="struts.action.extension" value="do"/>
do或action
<constant name="struts.action.extension" value="do,action"/>


truts2用来指定默认编码的
<constant name="struts.i18n.encoding" value="UTF-8"/>

 

改变常量后不许重启服务器
<constant name="struts.configuration.xml.reload" value="true"/>
系统默认为false 


便于排错,打印出更详细的错误信息
<constant name="struts.devMode" value="true">

 

设置浏览器是否缓存静态内容,默认为TRUE  开发阶段最好关闭
<constant name="struts.server.static.browserCache" valur="false"/>

 

默认的视图主题
<constant name="struts.ui.theme" value="simple"/>

 

与spring集成时,指定spring负责action对象的创建
<struts name="struts.objectFactory" value="spring"/>

 

上传文件大小限制
<struts name="struts.multipart.maxSize" value="10241024"/>

posted @ 2011-04-05 17:58 小果子 阅读(463) | 评论 (0)编辑 收藏
仅列出标题
共58页: First 24 25 26 27 28 29 30 31 32 Last