今天阅读相关书籍的时候看到 "进程中堆的最大申请数量" 这一问题,我们知道使用malloc分配内存是在堆Heap里面分配的,如果一台机器一共有8GB物理内存,空闲5GB,那么我们使用malloc( )就一定能够申请到这5GB内存吗?理论上来说确实如此,因为这些内存未被其它进程使用。但实际测试出来结果却可能令人疑惑。

本文测试环境如下:

1 qi@qi:~$ uname -a
2 Linux qi 5.4.0-89-generic #100~18.04.1-Ubuntu SMP Wed Sep 29 10:59:42 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux 

 一、首先需要考虑的几个问题

  1. 我们使用malloc( )申请到的是物理内存吗?
  2. 使用malloc( )能申请到的只有8GB的物理内存吗?
  3. malloc( )申请到的内存大小全都可以被用来memset( )吗?

以上三个问题,正是本次所要讨论的内容。现在假定认为以上三个陈述均正确,那么我们可以用以下程序测试malloc( )可以申请的内存大小:

 1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 unsigned long long int maximum = 0;
6
7 int main (int argc, char* argv[])
8 {
9 unsigned int block_size[] = {1024*1024, 1024, 1};
10 int i, count;
11
12 for( int i=0; i<3; i++) {
13 for(count=1;; count++) {
14 void *block = malloc(maximum + block_size[i]*count);
15 if( block ) {
16 //memset(block, 0, maximum + block_size[i]*count);
17 free(block);
18 maximum = maximum + block_size[i]*count;
19 } else {
20 break;
21 }
22 }
23 }
24
25 printf("maximum malloc size is %llu bytes \n", maximum);
26
27 return 0;
28 }

 运行以上程序,得到输出为:

1 root@qi:/home/qi/test_park/elf_load# ./main
2 maximum malloc size is 24587279333 bytes

 可以看到,以上测试程序最大申请到了 22.9GB 的内存,但是我的机器上实际内存有多少呢?如下:

1 qi@qi:~/test_park/elf_load$ free
2 total used free shared buff/cache available
3 Mem: 8011016 2373760 3517884 719508 2119372 4654640
4 Swap: 15999996 0 15999996

 很明显,机器上最大的物理内存也没到8GB,如果你了解swap 交换空间,可能会说Mem项和Swap项的total加起来似乎正好是22.9GB,但是另外一个问题有来了,那就是这些内存或者交换空间并不是全部空闲,包括系统内核和系统界面等等也要占用一部分物理内存,所以我们看到Mem项的 "available"的可用内存只有大约4.5GB,所以结果就是,malloc( )申请到的内存数量是远远大于我们实际的物理内存的。既然malloc( )函数的实际输出和我们的预期不相符,那是不是我们哪里用错了呢?不妨使用"man malloc"查看对其的官方解释:

1 NOTES
2 By default, Linux follows an optimistic memory allocation strategy. This means that when malloc() returns non-NULL there is no guarantee that the mem‐
3 ory really is available
. In case it turns out that the system is out of memory, one or more processes will be killed by the OOM killer. For more
4 information, see the description of /proc/sys/vm/overcommit_memory and /proc/sys/vm/oom_adj in proc(5), and the Linux kernel source file Documenta‐
5 tion/vm/overcommit-accounting.

果不其然,Note中说明了就算malloc( )返回非NULL指针也不能保证该指针指向的内存区域全都可以被该进程使用。那么为什么会这样呢?后面有提示,首先涉及到的最重要的一个设置就是 "/proc/sys/vm/overcommit_memory" 这一个文件,使用 "man proc" 找到有关其的说明:

 1        /proc/sys/vm/overcommit_memory
2 This file contains the kernel virtual memory accounting mode. Values are:
3
4 0: heuristic overcommit (this is the default)
5 1: always overcommit, never check
6 2: always check, never overcommit
7
8 In mode 0, calls of mmap(2) with MAP_NORESERVE are not checked, and the default
9 check is very weak, leading to the risk of getting a process "OOM-killed".
10
11 In mode 1, the kernel pretends there is always enough memory, until memory actually
12 runs out. One use case for this mode is scientific computing applications that
13 employ large sparse arrays. In Linux kernel versions before 2.6.0, any nonzero
14 value implies mode 1.
15
16 In mode 2 (available since Linux 2.6), the total virtual address space that can be
17 allocated (CommitLimit in /proc/meminfo) is calculated as
18
19 CommitLimit = (total_RAM - total_huge_TLB) *
20 overcommit_ratio / 100 + total_swap

可以看到,如果该文件内容为0,mmap(malloc的内部调用)将不检查,有导致使用不存在内存的风险,如果文件内容为1,则malloc( )可以申请的内存可以非常大,我的机器上经过测试可以达到90T,如果该文件内容为2,那么所有可以申请的内存为 "CommitLimit",具体可以通过公式或者 "cat /proc/meminfo | grep Limit"查看大小。那么这就能说通为什么上面的程序可以malloc( )出22GB多的内存了,查看 "/proc/sys/vm/overcommit_memory" 果不其然,内容为0:

1 root@qi:/home/qi/test_park/elf_load# cat /proc/sys/vm/overcommit_memory
2 0

以上回答了第2个问题中的一部分,那就是某些设置下,malloc( )可以申请到超出机器物理内存的大小,为什么说是一部分呢,因为可申请的内存不仅和上述设定相关,还和机器的swap space相关,如果你不了解或者没听过( 事实上在你给你机器装Linux系统的时候应该碰到过,那就是磁盘分区的时候会有一个swap设定)swap空间,只需要知道它是一种挂载在物理硬盘上,用来存放一些不太频繁使用的内存,是一种低速的物理内存的扩展,当物理内存不够用时,原先一些物理内存中不常访问的内容会被转移到这里以让出空间给其它进程。所以swap空间也可以被malloc( )申请到。

由此,第2个问题得到了全部的解答。这个时候你可能会说,第1个问题应该也有答案了,因为malloc( )不仅申请了8GB的物理内存,还申请了15GB的swap硬盘空间作为扩展内存,甚至还可以申请大约90TB的不存在的内存,所以第一个问题就解决了吗?

其实对,但也不全对,因为malloc( )这个时候申请了内存,但没有完全申请,这就涉及到一个叫做 "Lazy Allocation" 的东东,类似于fork的写时复制机制,当你使用malloc( )时,系统并没有真正从物理内存中分配,而是等到进程要操作时才提供allocation,这也就解释了我们刚开头申请了22.9GB的内存都还没有报段错误的原因。只有当你access这个内存区域的时候才会真正分配,所以我们可以大胆的在程序里面加上memset,把上面贴出的代码的memset那一行取消掉注释,然后再运行。如果你不想等太久,可以像我这样:

1 root@qi:/home/qi/test_park/elf_load# echo 0 >  /proc/sys/vm/overcommit_memory
2 root@qi:/home/qi/test_park/elf_load# swapoff -a
3 root@qi:/home/qi/test_park/elf_load# echo 2 > /proc/sys/vm/overcommit_memory

以上命令是把交换空间禁用,这样就可以减少可使用的内存了,关闭交换空间后,如果/proc/sys/vm/overcommit_memory内容为0,那么你可以malloc( )的内存大小应该为8GB左右,但是不是每一个字节都可以memset,大可以测试一下,会发现memset了6~7GB的内存空间后程序报错异常退出,这是因为这个时候可使用的内存也就这么大,这种情况下虽然使用malloc( )申请到的内存是不安全的。如果/proc/sys/vm/overcommit_memory内容为2,那么这个时候可申请的内存就得看 "CommitLimit" 了,在我的机器上测试是只能申请1.5GB左右,这种情况下无论如何也不会访问非法内存区域了,但是一个缺点是不能使用全部的空闲内存,只能修改相应的设置。

那么该如何知道实际可用的内存大小呢?一种解决方案是查看 "/proc/meminfo" 中的available memory,乘个安全系数再来申请。

以上,三个问题全都被解决,离专业的linuxer又近了一步~

拨开由问题《linux下malloc最大可申请的内存》带来的重重疑云的更多相关文章

  1. [Android Memory] Linux下malloc函数和OOM Killer

    http://www.linuxidc.com/Linux/2010-09/28364.htm Linux下malloc函数主要用来在用户空间从heap申请内存,申请成功返回指向所分配内存的指针,申请 ...

  2. Linux下查看内核、CPU、内存及各组件版本的命令和方法

    Linux下查看内核.CPU.内存及各组件版本的命令和方法 Linux查看内核版本: uname -a                        more /etc/*release       ...

  3. Linux下如何查看哪个进程占用内存多?

    1.top top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器 可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者 ...

  4. 方法:Linux 下用JAVA获取CPU、内存、磁盘的系统资源信息

    CPU使用率: InputStream is = null; InputStreamReader isr = null; BufferedReader brStat = null; StringTok ...

  5. Linux 下修改Tomcat使用的JVM内存大小

    我的服务器的配置: # OS specific support.  $var _must_ be set to either true or false. JAVA_OPTS="-Xms10 ...

  6. linux下使用free命令查看实际内存占用(可用内存)

    转:http://blog.is36.com/linux_free_command_for_memory/ linux下在终端环境下可以使用free命令看到系统实际使用内存的情况,一般用free -m ...

  7. Linux下jenkins改端口、解决内存溢出、版本升级

    1.新版本的jenkins修改端口新版本jenkins的配置文件在/etc/sysconfig/jenkinsvi /etc/sysconfig/jenkins找到JENKINS_PORT=" ...

  8. Linux 下安装sql server 时 2G内存限制的最新(2019-08-15) 解决方案

    关于 sqlserver 在linux下安装时有最小内存限制的问题,网上有很多类似的说明,那些操作都是正确的,如果不成功可能 “姿势”不对. 需要注意的是:不能使用最新版本!!!  不能在线下载!!! ...

  9. Linux下使用java获取cpu、内存使用率

    原文地址:http://www.voidcn.com/article/p-yehrvmep-uo.html 思路如下:Linux系统中可以用top命令查看进程使用CPU和内存情况,通过Runtime类 ...

随机推荐

  1. FastAPI 学习之路(十一)请求体 - 嵌套模型

    系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四) FastAPI 学习之 ...

  2. Python&Selenium 数据驱动测试【unittest+ddt+xml】

    一.摘要 本博文将介绍Python和Selenium做自动化测试时,基于unittest框架,借助ddt模块,使用xml文件作为测试输入. 二.xml文件 保存路径:D:\\Programs\\Pyt ...

  3. DPARAM

    中M_电子科技大学_计算机组成原理 双端口RAM Dual Port Access RAM 存储器不断接受CPU访问,还要频繁地和I/O设备通信.如果只有一套MAR,ID,MDR和读写电路.任一时刻只 ...

  4. Linux Manual

    man 命令用来访问存储在Linux系统上的手册页面.在想要查找的工具的名称前面输入man命 令,就可以找到那个工具相应的手册条目. 手册页不是唯一的参考资料.还有另一种叫作 info 页面的信息.可 ...

  5. 工厂模式--摆脱你日复一日new对象却依旧单身的苦恼!

    前言 每每谈及到Java,就不免会想到一个悲伤的事实:你是否每天都在new对象,却依然坚守在单身岗上屹立不倒.(所谓面向对象编程hhh),这篇来学一下工厂模式,摆脱new对象的苦恼! 知识点 传统工厂 ...

  6. DataX的安装及使用

    DataX的安装及使用 目录 DataX的安装及使用 DataX的安装 DataX的使用 stream2stream 编写配置文件stream2stream.json 执行同步任务 执行结果 mysq ...

  7. 什么,你还使用 webpack?别人都在用 vite 搭建项目了

    一.vite 到底是干嘛的? vite 实际上就是一个面向现代浏览器,基于 ES module 实现了一个更轻快的项目构建打包工具. vite 是法语中轻快的意思. vite 的特点: 1.轻快的冷服 ...

  8. Noip模拟34 2021.8.9

    T1 Merchant 一眼二分,然后想了想维护凸包,好像并没有什么关系, 然后又想了想维护一个栈,发现跳指针细节过多不想打 最后直接打了二分,大点跑的飞快,感觉比较稳,出来$78$分 是没用神奇的$ ...

  9. si macro macro

    获取 buf 里的 symbol cbuf = BufListCount() msg(cbuf) ibuf = 0 while (ibuf < cbuf) { hbuf = BufListIte ...

  10. cf14C Four Segments(计算几何)

    题意: 给四个线段(两个端点的坐标). 判断这四个线段能否构成一个矩形.(矩形的四条边都平行于X轴或Y轴) 思路: 计算几何 代码: class Point{ public: int x,y; voi ...