繁体   English   中英

如何在 Linux 上检查进程的堆大小

[英]How to check heap size for a process on Linux

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

PS:这里有一个非常相似的问题,但我的回答不清楚。

堆和内存管理是由 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_AS或使用ulimit -v命令对进程可以分配的虚拟内存设置限制。 不管上面描述的过量使用设置如何,如果进程尝试分配比限制更多的内存,内核将拒绝它并且malloc()将返回 NULL。 请注意,在现代 Linux 内核(包括整个 2.6.x 系列)中,对驻留大小的setrlimit()带有RLIMIT_RSSulimit -m命令的setrlimit() )是无效的。

下面的会话在具有 4GB RAM 和 8GB 交换的内核 2.6.32 上运行。

$ 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()映射的页面)或进程的最大内存映射数被超过了。

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

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

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

#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()确认内存已给出并使用。

为了在具有 256 MiB ram 和 400MiB 交换的 Ubuntu 10.04x86 VM 上进行比较,ulimit 报告是memory size (kbytes, -m) unlimited而我的小程序报告了 524.288.000 字节,这大致是 ram 和交换的组合,打折其他软件和内核使用的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) ;

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

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

发生这种情况的原因特定于您的系统。

当一个进程被加载时,它被分配到某个地址的内存,该地址是该进程的系统断点。 超出该地址的内存未映射到进程。 因此,当进程“命中”“断点”点时,它会从系统请求更多内存,一种方法是通过系统调用sbrk
malloc会在malloc做到这一点,但在您的系统中由于某种原因它失败了。

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

我想在之前的答案中补充一点。

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

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

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

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

您可以从顶部找到您的 webapp/java 进程的进程 ID。 使用 jmap heap - 获取堆分配。 我在 AWS-Ec2 上针对弹性豆茎测试了这个,它给出了分配的堆。 这是elasticbean stalk中通过环境属性的详细答案Xmx设置

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM