问题起因

问题的起因是因为使用了一个apr的服务,产生了巨大的virtual memory,具体的表现是,在top中可以看到该进程的VIRT和RES,VIRT比实际上使用的要大很多。

在google上找到如下文章

https://lists.apache.org/thread/yvwxmssr90y2nnozj949w5gsg9gpxn1p

怕有人访问不了,直接把原文贴在这里

I have noticed that my multithreaded APR program consumes a *very*
large amount of virtual memory per thread and I can't think of a
reason why. I am running Debian 3.1 (latest kernel 2.6.8-3-686) and I
tried the test program below with both the APR 0.9.x that comes with
Debian and with the latest APR 1.2.x version.
我注意到多线程的APR程序每一个线程会消耗很大的virtual memory,我没有想明白是为什么。
我使用的是Debian 3.1的系统,基于apr版本 APR 0.9.x和 APR 1.2.xIn both cases I end up with around 800 MB of virtual RAM for 100 threads:
20385 ivanr 16 0 802m 956 1800 S 0.0 0.4 0:00.02 test
在如下两个例子中,运行100个线程大致使用了800MBvirtual RAMAm I doing something wrong or is this a bug? Any help is greatly appreciated!void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
apr_thread_exit(thread, 0);
}int main(int argc, const char * const argv[]) {
apr_pool_t *pool;
int i;apr_app_initialize(&argc, &argv, NULL);
atexit(apr_terminate);apr_pool_create(&pool, NULL);for(i = 0; i < 100; i++) {
apr_thread_t *thread = NULL;
apr_thread_create(&thread, NULL, thread_worker, NULL, pool);
}apr_sleep(1000 * 1000 * 1000);
}

VIRT & RES & SHR

如果你执行top看一下进程的内存占用,会得到如下几列

  • VIRT
    它是程序“需要”内存的总和,包含了加载的动态库内存,与其他进程共享的内存,以及分配给他的内存;
    但是如果程序申请了100m但是仅仅使用了10m,这里还是按照100m计算统计

  • RES
    它是程序正在使用内存的总和,一个主要区别是如果程序申请了100m但是仅仅使用了10m,这里是按照10m计算统计

  • SHR
    加载的库的内存,如果仅仅是使用库里的一些函数,但是整个库还是会加载入内存。

一个“空白”程序,如下,它会占用多少VIRT,RES,SHR?

int main(int argc, const char * const argv[]) {return 0;
}


它占用了4164kB的VIRT, 368KB的RES, 268KB的SHR。

SHR基本上为动态库加载。一个“什么都没有的"c程序,基础的链接是这样的

那么程序”自身“多大呢? RES-SHR = 368- 268 = 100KB

程序总共”需要“的大小 VIRT = 4164kB

如果我们申请1m内存再释放它,是否可以跟上述程序暂用内存一致呢?
来试一下

int main(int argc, const char * const argv[]) {char *p = (char *)calloc(1048576, sizeof(char)); free(p); // break 1return 0; // break 2
}

这里我们增加2个断点,来观察内存使用的变化;

break1


与上面”空白“程序相比,此时RES是增加1k; 但是VIRT也增长了1k

break2

与上面”空白“程序相比,VIRT是回到了原点。 RES仍有所增加,但是可以理解,毕竟多了几行代码,code也是要占用空间的;
SHR也增加了一点(这个是为什么?)

换成malloc呢?

int main(int argc, const char * const argv[]) {char *p;for (int i=0; i<1048576; i++)p = malloc(sizeof(char));//free(p);return 0;
}

在不释放内存的情况下,RES可以涨到33M;VIRT涨到37M

在释放后,内存情况大体也相当; 说明多次调用malloc还是有一定开销的。(这里换成calloc也一样)

int main(int argc, const char * const argv[]) {char *p;for (int i=0; i<1048576; i++) {p = malloc(sizeof(char));free(p);}return 0;
}

apr_thread测试

1. 使用样例代码(不做thread_join)

上面的帖子跟遇到的情况有些类似,所以我把他的样例程序跑了一下

#include <apr-1/apr.h>
#include <apr-1/apr_pools.h>
#include <apr-1/apr_thread_proc.h>#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {
apr_thread_exit(thread, 0);
}int main(int argc, const char * const argv[]) {apr_pool_t *pool;int i;apr_status_t status = 0;apr_app_initialize(&argc, &argv, NULL);atexit(apr_terminate);apr_pool_create(&pool, NULL);apr_thread_t *thread[THREAD_NUM];for(i = 0; i < THREAD_NUM; i++) {apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool);}apr_sleep(1000 * 1000 * 1000);
}

样例中运行100个apr_thread

这个是样例代码的结果。


可以看到RES仅仅1.7M,但是VIRT确实906M,将近一个G了。这个差距也太大了。确实有文章里说的问题。

2. 执行thread_join

如果将所有线程join,如下

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {apr_thread_exit(thread, 0);
}int main(int argc, const char * const argv[]) {apr_pool_t *pool;int i;apr_status_t status = 0;apr_app_initialize(&argc, &argv, NULL);atexit(apr_terminate);apr_pool_create(&pool, NULL);apr_thread_t *thread[THREAD_NUM];for(i = 0; i < THREAD_NUM; i++) {apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool);}// for(i = 0; i < THREAD_NUM; i++) {apr_thread_join(&status, thread[i]);}apr_sleep(1000 * 1000 * 1000);return 0;
}

RES1.3M,几乎差不多;VIRT120M,比不join少了很多;但是仍有近100倍差距

3. 再做apr_pool_clear

如果在thread_join后面再调用apr_pool_clear(pool),主动clear一下pool呢?


几乎没有变化。

4. 移除apr_thread_exit

既然最后都有thread_join,把线程中的apr_thread_exit移除试试呢?

代码如下

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {//apr_thread_exit(thread, 0);
return NULL;
}int main(int argc, const char * const argv[]) {apr_pool_t *pool;int i;apr_status_t status = 0;apr_app_initialize(&argc, &argv, NULL);atexit(apr_terminate);apr_pool_create(&pool, NULL);apr_thread_t *thread[THREAD_NUM];for(i = 0; i < THREAD_NUM; i++) {apr_thread_create(&thread[i], NULL, thread_worker, NULL, pool);}for(i = 0; i < THREAD_NUM; i++) {apr_thread_join(&status, thread[i]);}apr_sleep(1000 * 1000 * 1000);return 0;
}

对比发现;RES和SHR几乎没有变化;但是VIRT居然只有52M,比之前少了一半还多。

这么看来,如果最后有join的动作,还是不要执行apr_thread_exit的比较节省内存。

5. 将线程detach运行

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {//apr_thread_exit(thread, 0);
return NULL;
}int main(int argc, const char * const argv[]) {apr_pool_t *pool;int i;apr_status_t status = 0;apr_threadattr_t *thread_attr;apr_app_initialize(&argc, &argv, NULL);atexit(apr_terminate);apr_pool_create(&pool, NULL);apr_threadattr_create(&thread_attr, pool);apr_threadattr_detach_set(thread_attr, 1);apr_thread_t *thread[THREAD_NUM];for(i = 0; i < THREAD_NUM; i++) {assert(apr_thread_create(&thread[i], thread_attr, thread_worker, NULL, pool)==APR_SUCCESS);}apr_sleep(1000 * 1000 * 1000);return 0;
}

与joinable的thread几乎一样。

如果还原apr_thread_exit,也跟joinable的thread几乎一样


所以detach对内存几乎没有影响。

6. 限制thread的栈大小

#define THREAD_NUM 100void* APR_THREAD_FUNC thread_worker(apr_thread_t *thread, void *data) {//apr_thread_exit(thread, 0);
return NULL;
}int main(int argc, const char * const argv[]) {apr_pool_t *pool;int i;apr_status_t status = 0;apr_threadattr_t *thread_attr;apr_app_initialize(&argc, &argv, NULL);atexit(apr_terminate);apr_pool_create(&pool, NULL);apr_threadattr_create(&thread_attr, pool);//apr_threadattr_detach_set(thread_attr, 1);apr_threadattr_stacksize_set(thread_attr,10240); //10kapr_thread_t *thread[THREAD_NUM];for(i = 0; i < THREAD_NUM; i++) {assert(apr_thread_create(&thread[i], thread_attr, thread_worker, NULL, pool)==APR_SUCCESS);}for(i = 0; i < THREAD_NUM; i++) {apr_thread_join(&status, thread[i]);}apr_sleep(1000 * 1000 * 1000);return 0;
}

每一个线程的栈大小限制为10k,好像没有什么变化??

每个thread的栈默认是多少呢? 一般来说,默认为8M,即8,388,608bytes

可以通过ulimit -s或者ulimit -a看一下

可以通过**apr_threadattr_stacksize_set(thread_attr,8388608);**来还原一下默认值,跑出来结果跟不设置差不多,VIRT为52M;


好像真没有什么变化。

pthread测试

作为对比,我们同样的使用pthread来做一轮测试

1. 不做pthread_join

#include <pthread.h>
#include <assert.h>
#include <zconf.h>#define THREAD_NUM 100static void *thread_worker(void *data) {pthread_exit(NULL);return NULL;
}int main(int argc, const char *const argv[]) {int i = 0;pthread_t thread[THREAD_NUM];for (i = 0; i < THREAD_NUM; i++) {assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0);}sleep(1000000);return 0;
}

与apr_thread对比,VIRT893M 基本相当; RES 1.2M比之前的1.7M了一点;SHR 0.4M也小了一点;

注释掉 pthread_exit的结果如下:
VIRT826M,RES 1.1M,SHR 0.4M

做pthread_join

static void *thread_worker(void *data) {pthread_exit(NULL);return NULL;
}int main(int argc, const char *const argv[]) {int i = 0;pthread_t thread[THREAD_NUM];for (i = 0; i < THREAD_NUM; i++) {assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0);}for (i = 0; i < THREAD_NUM; i++) {pthread_join(thread[i], NULL);}sleep(1000000);return 0;
}

相比apr_thread,VIRT 106M/ RES 0.7M/SHR 0.5M 都小一些

3. 去除pthread_exit

#define THREAD_NUM 100static void *thread_worker(void *data) {//pthread_exit(NULL);return NULL;
}int main(int argc, const char *const argv[]) {int i = 0;pthread_t thread[THREAD_NUM];for (i = 0; i < THREAD_NUM; i++) {assert(pthread_create(&thread[i], NULL, thread_worker, NULL) == 0);}for (i = 0; i < THREAD_NUM; i++) {pthread_join(thread[i], NULL);}sleep(1000000);return 0;
}

5. 限制stacksize

#define THREAD_NUM 100static void *thread_worker(void *data) {//pthread_exit(NULL);return NULL;
}int main(int argc, const char *const argv[]) {int i = 0;int stacksize = 10240;pthread_t thread[THREAD_NUM];pthread_attr_t  attr;pthread_attr_init(&attr);pthread_attr_setstacksize(&attr, stacksize);for (i = 0; i < THREAD_NUM; i++) {assert(pthread_create(&thread[i], &attr, thread_worker, NULL) == 0);}pthread_attr_destroy(&attr);sleep(1000000);return 0;
}

相对于不设置大小,内存各项指标基本不变。(同apr_thread结果)

6. 结论

pthread的内存占用略比apr_thead少,但是整体都出现VIRT比RES大几百倍的情况; 尤其是pthread_exit的使用,似乎对VIRT的参数影响较大。


优化解决方案

apr_thread使用内存之谜相关推荐

  1. Readyfor4GB帮你的32位Win7用4G内存

    虽然现在64位操作系统可以很好的支持大容量内存,但是由于以前少数的软件不能运行在64位的WIN7上,不管多少,毕竟不爽,所以我还是偏爱使用32位的Windows7,课时由于微软当初设置操作系统的时候, ...

  2. 4G内存为什么会少800M

    文章来源:http://nc.mofcom.gov.cn/news/P1P45I8395107.html 所买的内存绝对是正品,质量不回存在问题,那4G的内存为什么会减少800M呢?这儿将从软件,硬件 ...

  3. 【转】详解4G内存与CPU,BIOS和操作系统之间的牵绊

    因为内存价格的持续走低,目前各大内存厂商相继推出了单条2GB的DDR2 800内存,这些内存给人最大的感觉就是价格便宜量又足.很多用户就直接买了两条2GB的内存,想组成双通道使用.可拿回家一看,原本4 ...

  4. Readyfor4GB帮你的32位Win7用4G内存_我是亲民_新浪博客

    虽然现在64位操作系统可以很好的支持大容量内存,但是由于以前少数的软件不能运行在64位的WIN7上,不管多少,毕竟不爽,所以我还是偏爱使用32位的Windows7,课时由于微软当初设置操作系统的时候, ...

  5. 32 位 Win7 用 4G 内存破解加去水印全攻略

    虽然现在64位操作系统可以很好的支持大容量内存,但是我们不得不提到现在64位的软件还不是非常的多,而且Vista和Win7的64位版本在兼容性上还是有所不足.另一反面,由于现在内存价格的走低,4GB容 ...

  6. win7下4GB内存提示:找到4GB,可用2GB的解决方法--让你的win7支持4G内存

    今天我给自己的本本扩展了一下内存,但是发现在系统信息中提示"找到4GB,可用2GB"的怪异信息,百度了很久终于悟出了其中的奥妙,在新浪的一篇技术文章中,找到了解决方法,下面是该文章 ...

  7. 安卓 background的图片随着textview的大小而改变_苹果4G内存吊打安卓8G内存?看看苹果是怎么虐安卓的!...

    戳上面的蓝字关注我们哦! 大家好!我是小马哥! 精致有趣的科技数码体验与测评 尽在"来回科技" 有锁机全面科普 科普丨什么是有锁机/卡贴机 信号测试丨有锁机vs无锁机 全新来回商城 ...

  8. C++:关于string

    C++:关于string 刷LeetCode的时候明明自己写的算法思路和题解一样,但是运行却速度慢了几倍,内存消耗谜之巨大,找了半天原来是数据结构和赋值运算的问题.这次踩坑了赶紧记下来,防止以后再出现 ...

  9. 难道这又是个未解之谜?--- 关于DLL中使用ADODATASET出错的问题

    难道这又是个未解之谜?--- 关于DLL中使用ADODATASET出错的问题 Delphi / Windows SDK/API http://www.delphi2007.net/DelphiAPI/ ...

  10. s5 android5.0内存泄漏,android-最初从位图泄漏了未引用的byte [],但被回收的()导致内存泄漏(直到活动停止)...

    我的位图内存泄漏导致内存不足.我在Android 5.0(Samsung S5)上运行了测试.我已经使用Android Studio(1.5.1 2.0.0预览版7)调查了此问题. HPROF内存转储 ...

最新文章

  1. 腾讯广告广点通API接入文档(Android)
  2. “CTO嫌弃我读书太少!” 假期里如何更高效的阅读?老K的3张专辑文章,读个痛快!...
  3. qt带小数点的数字串显示,Qt-自定义小数点和千位分隔符
  4. 记录spark-yarn模式下提交自己写的java程序
  5. 自用开源/免费软件收集
  6. C++ Applications
  7. 日本老人租妻女,“共享家庭”能用来抵抗孤独吗?
  8. 无法复制文件到远程桌面的解决办法
  9. 再爆hzhost6.5虚拟主机管理系统的SQL注入漏洞3
  10. 微信服务号开发说明:测试号申请、自定义菜单添加第三方连接
  11. [Vue][transition]Vue中实现类似JQuery中slideUp slideDown的滑动显示隐藏过渡动画效果
  12. Xtract or Ucfyber的最条理的整理入门级教程
  13. linux好用的office软件,MS Office Online——免费好用的在线办公软件
  14. 用于前列腺近距离放疗的MRI / TRUS数据融合初步结果
  15. 纯jsp实现数据库的连接和验证登录
  16. 玩转百度即用API(5)——空气质量指数查询
  17. AdEx、Ardor、Bluzelle项目评级更新 | TokenInsight
  18. R语言中级图形:相关系数图及马赛克图
  19. 如何设计出用户喜爱的API
  20. 农产品追溯系统免费源代码分享及下载---第2篇

热门文章

  1. 如何从chrome获取你需要的缓存视频
  2. Lookup 组件用法全解
  3. 对文件夹中文件进行批量重命名
  4. 【贝尔链创始人Vincent: 区块链不仅是技术革命 更是认知革命】
  5. 如何向面试官介绍你的项目(面试技巧)
  6. 天翼宽带怎么开虚拟服务器,怎么设置天翼宽带猫的无线路由器
  7. Excel单元格保护
  8. 微信小程序怎么设置服务器上,如何为微信小程序设置服务器地址?-微信小程序服务器诗...
  9. 苹果电脑入门:复制、粘贴、剪切文件
  10. Hadoop的容错性