进程空间
进程运行时需要在内核中占据一段内存空间,用以存储程序和数据。
每个进程空间分布如下所示:
-
text段:
代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。 -
data段:
数据段(data segment)通常用来存放程序中已初始化的全局变量数据段属于静态内存分配。 -
bss段:
bss(Block Started by Symbol) 通常用来存放程序中未初始化的全局变量。bss段属于静态内存分配。 -
堆(heap):
堆用于存放动态变量,它的大小并不固定,可动态扩张或缩减。
主要由程序员手工分配:
当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;
当利用free等函数释放内存时,被释放的内存从堆中被剔除。 -
栈(stack):
存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回,在程序块开始时自动分配内存,结束时自动释放内存,主要由编译器自动管理。
在函数被调用时,其参数会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。
注:堆和栈的区别参见末尾附图1.
可能产生的问题
内存泄漏
当程序中使用malloc的时候,堆(heap)会向上增长,其增长的部分就成为malloc从内存中分配的空间。malloc开辟的空间会一直存在,直到程序员手工用free系统调用来释放,或者进程结束。
内存泄漏(memory leakage), 就是指我们没有释放不再使用的堆空间,导致堆不断增长,而内存可用空间不断减少。
栈溢出
栈和堆的大小则会随着进程的运行增大或者变小。当栈和堆增长到两者相遇时候,也就是内存空间图中stack和heap之间的可用内存区域完全耗尽时,进程会出现栈溢出(stack overflow)的错误,导致进程终止。
垃圾回收机制(Garbage-Collection)
由上文可知,进程的内存管理是十分重要的,内存需要被合理分配使得进程能够正常运行,避免出现内存泄漏、栈溢出等异常情况。
内存空间是有限的,不能一味地分配内存,需要有人负责回收分配出去的内存空间,如果交由程序员手动进行内存管理,程序员就比较累,没法完全专注于业务逻辑的实现,影响开发效率,而且手动管理内存是纯技术活,人工错误很常见,这就有了垃圾回收机制(Garbage-Collection),程序员只要专注于业务逻辑的实现,尽管用内存,不必关心内存的回收。
垃圾回收机制(Garbage-Collection)的职能:
识别那些垃圾对象,从垃圾对象那回收内存。并分配给新生成的对象使用。
python采用的是引用计数
机制为主,标记-清除
和分代回收
两种机制为辅的策略
引用计数机制
优点:简单且具备实时性:一旦一个对象的引用计数归零,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
缺点:维护引用计数消耗资源,无法回收循环引用对象。
频繁的垃圾回收会降低程序执行效率,Python只会在特定条件下,自动启动垃圾回收。Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪,当被分配对象的计数值与被释放对象的计数值之差达到某一阈值时便启动垃圾回收机制。
Python也采用了分代回收的策略。基于“存活时间越久的对象越不容易成为垃圾。”这一假说,若某一对象在经历多次垃圾回收后依然健在,则提升该对象的等级。对象等级一共分0,1,2三代,每次垃圾回收从0代开始,经过一定次数对0代对象的垃圾回收后,便启动对0代和1代对象的垃圾回收,1代对象经过一定次数的的垃圾回收,便启动对0代1代2代即所有对象的垃圾回收。
查看方法
import gc
gc.set_threshold(700, 10, 5)
返回(700, 10, 10),后面的两个10是与分代回收相关的阈值,后面可以看到。700即是垃圾回收启动的阈值。
若要手动启动垃圾回收,使用gc.collect()。
附图1