一,从进程角度看堆区内存申请与释放问题
1,c语言中的内存泄漏
内存溢出:申请内存时,没用足够的内存可以使用。
内存泄露:严格来说,只有对象不会再被程序用到了,但是GC又不能回收它们的情况,才叫内存泄漏
宽泛的讲,实际情况中很多时候一些不太好的实践会导致对象的生命周期变得很长甚至导致OOM,也叫“内存泄漏”
申请了内存用完了不释放,如申请了1024M内存,分配了512M内存一直不回收,那么可用内存就只有512M,仿佛泄漏掉一部分。
内存泄漏的增多,最终会导致内存溢出。
2,malloc申请一块空间,直到进程结束都不释放,是否造成内存泄露
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>int main()
{
char * s = (char*)malloc(128);
assert(s != NULL);
exit(0);
}
如果程序持续运行,那么内存是不会被释放的,但如果如我给出的程序一样,那么内存将在程序结束的时候有系统自动回收。
3, malloc 申请 1G 的内存空间是否能成功?
我们试图申请1G空间,并且如果申请失败则打印error exit。
从上图我们可以看出,我们成功申请了1G空间。
4,在物理内存只有 2G 的系统中,malloc 能否申请 2G 空间?
1,地址空间限制:首先,我们需要考虑进程的地址空间限制。在 32 位系统中,一个进程的地址空间通常被限制在 2 GB 到 4 GB 之间,这取决于操作系统和配置。由于地址空间需要为代码、数据、堆栈和其他内存段共享,因此实际上可用的堆内存空间可能会小于 2 GB。在 64 位系统中,地址空间要大得多,通常不会受到这种限制。
2,系统预留和其他进程的内存使用:即使系统总共有 2 GB 内存,操作系统的内核和系统服务也会占用一部分内存。此外,其他正在运行的进程也会占用内存。因此,可用的内存量可能会小于 2 GB。
3,虚拟内存和交换空间:即使物理内存不足,现代操作系统通常会使用虚拟内存和交换空间来扩展可用内存。这意味着,即使物理内存不足,malloc
也可能成功分配 2 GB 内存,因为系统可以使用磁盘上的交换空间来模拟更多的内存。
5,malloc 与 fork
父进程堆区申请的空间复制后,子进程也会有一份,也需要释放?
观察上面的程序,我们发现,父子进程都成功打印了数据,这是否意味着,在复制进程后,是否子进程也拥有了malloc出来的空间呢。
在linux操作系统中,当你使用fork系统调用创建一个子进程时,子进程会获得父进程的虚拟内存空间的副本,包括代码段、数据段、堆栈和堆。这意味着,如果父进程在fork之前使用malloc分配了内存,那么子进程也会有这些内存区域的副本。
这个副本是写时复制的(copy-on-write),这意味着在父进程或子进程尝试修改这些内存页之前,它们实际上并不会在物理内存中复制。只有在其中一个进程尝试写入时,操作系统才会为写入的页创建一个物理内存中的副本。
关于内存释放,有以下三点需要注意:
1,父进程释放内存:如果父进程在fork之后释放了内存,这不会影响子进程的内存副本。子进程仍然可以访问和修改它的内存副本。
2,子进程释放内存:同样,如果子进程释放了内存,这不会影响父进程的内存。每个进程都有自己的地址空间和独立的内存管理。
3,避免内存泄露:在fork之后,如果两个进程都试图访问和释放相同的内存区域,可能会导致双重释放的问题,这是需要避免的。通常,子进程会在exec系统调用之后立即执行新的程序,这将替换掉当前进程的地址空间,包括堆。在这种情况下,子进程不需要关心父进程的malloc分配的内存释放问题,因为新的程序会重新初始化其内存空间。