【问题标题】:How to check heap size for a process on Linux如何在 Linux 上检查进程的堆大小
【发布时间】:2012-01-12 02:41:53
【问题描述】:

我正在编写一些代码,但它一直在崩溃。后来在挖掘转储后,我意识到我超出了最大堆限制(如果我添加了对 malloc 的检查,生活会更轻松)。虽然我解决了这个问题,但有什么方法可以增加我的堆大小吗?

PS:这里是similar question,但我不清楚回复。

【问题讨论】:

  • 堆不是分配给你的程序的几乎整个虚拟内存吗?这是 32 位平台(或更小)吗?您要分配多少内存?

标签: c linux memory crash heap-memory


【解决方案1】:

堆和内存管理是您的 C 库(可能是 glibc)提供的一种工具。每次您执行malloc() 时,它都会维护堆并向您返回内存块。它不知道堆大小限制:每次您请求比堆上可用的内存更多的内存时,它都会向内核请求更多(使用sbrk()mmap())。

默认情况下,内核几乎总是会在询问时为您提供更多内存。这意味着malloc() 将始终返回一个有效地址。只有当您第一次引用分配的页面时,内核才会真正为您找到一个页面。如果它发现它不能交给你,它会运行一个 OOM 杀手,根据称为 badness 的某种度量(包括你的进程及其子进程的虚拟内存大小、nice 级别、总体运行时间等)选择一个受害者并向其发送SIGTERM。这种内存管理技术称为 overcommit,当/proc/sys/vm/overcommit_memory 为 0 或 1 时由内核使用。有关详细信息,请参阅内核文档中的 overcommit-accounting

通过将 2 写入 /proc/sys/vm/overcommit_memory,您可以禁用过度使用。如果你这样做,内核会在承诺之前检查它是否有内存。如果没有更多可用内存,这将导致 malloc() 返回 NULL。

您还可以使用setrlimit()RLIMIT_ASulimit -v 命令设置进程可以分配的虚拟内存限制。不管上面描述的 overcommit 设置如何,如果进程试图分配超过限制的内存,内核将拒绝它并且malloc() 将返回 NULL。请注意,在现代 Linux 内核(包括整个 2.6.x 系列)中,驻留大小的限制(setrlimit()RLIMIT_RSSulimit -m 命令)无效。

以下会话在内核 2.6.32 上运行,具有 4GB RAM 和 8GB 交换空间。

$ cat bigmem.c
#include <stdlib.h>
#include <stdio.h>

int main() {
  int i = 0;
  for (; i < 13*1024; i++) {
    void* p = malloc(1024*1024);
    if (p == NULL) {
      fprintf(stderr, "malloc() returned NULL on %dth request\n", i);
      return 1;
    }
  }
  printf("Allocated it all\n");
  return 0;
}
$ cc -o bigmem bigmem.c
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ sudo bash -c "echo 2 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
2
$ ./bigmem
malloc() returned NULL on 8519th request
$ sudo bash -c "echo 0 > /proc/sys/vm/overcommit_memory"
$ cat /proc/sys/vm/overcommit_memory
0
$ ./bigmem
Allocated it all
$ ulimit -v $(( 1024*1024 ))
$ ./bigmem
malloc() returned NULL on 1026th request
$

在上面的示例中,永远不会发生交换或 OOM 终止,但如果进程实际尝试访问所有分配的内存,这将发生显着变化。

直接回答您的问题:除非您使用ulimit -v 命令明确设置了虚拟内存限制,否则除了机器的物理资源或地址空间的逻辑限制(与 32 位系统相关)之外,没有堆大小限制。您的 glibc 将继续在堆上分配内存,并随着堆的增长向内核请求越来越多的内存。如果所有物理内存都用完,最终你可能会交换得很糟糕。一旦交换空间耗尽,内核的 OOM 杀手将杀死一个随机进程。

但是请注意,内存分配失败的原因可能不止是缺少可用内存、碎片或达到配置的限制。 glib 的分配器使用的 sbrk()mmap() 调用有它们自己的失败,例如程序中断到达另一个已分配的地址(例如共享内存或先前使用mmap() 映射的页面)或已超出进程的最大内存映射数。

【讨论】:

  • 如果没有存储sbrk调用的返回值,是否可以直接从用户态获取start_brk
【解决方案2】:

堆通常与架构上可寻址的虚拟内存一样大。

您应该使用ulimit -a 命令检查您的系统当前限制并查找此行 max memory size (kbytes, -m) 3008828,在我的 OpenSuse 11.4 x86_64 上使用 ~3.5 GiB 内存的这行表示我每个进程大约有 3GB 内存。

然后您可以使用这个简单的程序来真正测试您的系统,以检查每个进程的最大可用内存:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char* argv[]){
        size_t oneHundredMiB=100*1048576;
        size_t maxMemMiB=0;
        void *memPointer = NULL;
        do{
                if(memPointer != NULL){
                        printf("Max Tested Memory = %zi\n",maxMemMiB);
                        memset(memPointer,0,maxMemMiB);
                        free(memPointer);
                }
                maxMemMiB+=oneHundredMiB;
                memPointer=malloc(maxMemMiB);
        }while(memPointer != NULL);
        printf("Max Usable Memory aprox = %zi\n",maxMemMiB-oneHundredMiB);
        return 0;
}

此程序以 100MiB 为增量获取内存,显示当前分配的内存,在其上分配 0,然后释放内存。当系统无法提供更多内存时,返回 NULL 并显示最终的最大可用内存量。

需要注意的是,您的系统将在最后阶段开始大量交换内存。根据您的系统配置,内核可能会决定终止某些进程。我使用 100 MiB 的增量,因此某些应用程序和系统有一些喘息的空间。你应该关闭任何你不想崩溃的东西。

话虽如此。在我写这篇文章的系统中,没有任何崩溃。上面的程序报告几乎与ulimit -a 相同。不同的是,它实际测试了内存,并通过memset() 确认内存已被给予和使用。

对于 Ubuntu 10.04x86 VM 与 256 MiB 内存和 400MiB 交换空间的比较,ulimit 报告为memory size (kbytes, -m) unlimited,而我的小程序报告了 524.288.000 字节,这大致是 ram 和 swap 的组合,折扣 ram 使用其他软件和内核。

编辑:正如 Adam Zalcman 所写,ulimit -m 在较新的 2.6 及更高版本的 linux 内核上不再受到尊重,所以我坚持纠正。但是ulimit -v 很荣幸。对于实际结果,您应该将 -m 替换为 -v,并查找 virtual memory (kbytes, -v) 4515440。我的 suse 盒子的 -m 值与我的小实用程序报告的值似乎只是偶然的。请记住,这是内核分配的虚拟内存,如果物理内存不足,则需要交换空间来弥补。

如果你想知道有多少物理内存可用而不干扰任何进程或系统,你可以使用

long total_available_ram =sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE) ;

这将排除缓存和缓冲内存,因此这个数字可能远小于实际可用内存。操作系统缓存可以非常大,并且它们的逐出可以提供所需的额外内存,但这由内核处理。

【讨论】:

  • ulimit -m 在 2.4.29 之后的 Linux 内核中没有任何作用。
  • “无限”没有意义,对吧?必须有一个限度。 ulimit -m 和 ulimit -v 在我的 ubuntu 上都返回无限制。我认为真正找出答案的最佳方法是运行您的实用程序。
【解决方案3】:

我认为您最初的问题是 malloc 未能在您的系统上分配请求的内存。

为什么会发生这种情况取决于您的系统。

当一个进程被加载时,它被分配到某个地址的内存,该地址是该进程的系统断点。在该地址之外,该进程的内存未映射。因此,当进程“击中”“断点”时,它会从系统请求更多内存,一种方法是通过系统调用 sbrk
malloc 会在后台执行此操作,但在您的系统中某些原因它失败了。

这可能有很多原因,例如:
1)我认为在 Linux 中最大内存大小是有限制的。我认为它是ulimit,也许你击中了它。检查它是否设置为限制
2) 也许您的系统负载过大
3) 您的程序内存管理不善,最终导致内存碎片化,因此malloc 无法获得您请求的块大小。
4) 您的程序损坏了 malloc 内部数据结构,即错误的指针使用
等等

【讨论】:

  • 答案 3 就是这种情况。我尝试检查 ulimit,但没有找到任何有关堆大小的东西。是的,我可以使用 ulimit 增加堆栈大小。确实,正如我从 strace 看到的那样,它的 sbrk 失败了o/p.(错误代码 ENOMEM)
【解决方案4】:

我想在前面的答案上加一点。

应用程序会产生 malloc() 返回“实心”块的错觉;实际上,缓冲区可能分散、粉碎,存在于许多 RAM 页上。这里的关键事实是:一个进程的虚拟内存,包含它的代码或包含一个大数组的东西,必须是连续的。我们甚至承认代码和数据是分开的;大数组 char str[universe_size] 必须是连续的。

现在:单个应用程序可以任意扩大堆,分配这样的数组吗?

如果机器中没有其他东西在运行,答案可能是“是”。堆可能非常大,但它必须有边界。在某些时候,对 sbrk()(在 Linux 中,简而言之,“扩大”堆的函数)的调用应该会偶然发现为另一个应用程序保留的区域。

link 提供了一些有趣且清晰的示例,请查看。我没有找到关于 Linux 的信息。

【讨论】:

    【解决方案5】:

    您可以从顶部找到您的 webapp/java 进程的进程 ID。 使用 jmap heap - 获取堆分配。我在 AWS-Ec2 上针对弹性 beantalk 进行了测试,它提供了分配的堆。这是详细的答案 Xmx settings in elasticbean stalk through environment properties

    【讨论】:

      猜你喜欢
      • 2012-03-04
      • 2011-01-22
      • 2011-05-09
      • 2017-02-28
      • 2015-11-13
      • 2011-10-18
      • 1970-01-01
      • 2018-12-11
      • 2010-12-13
      相关资源
      最近更新 更多