内存回收

简介

内存回收是内存管理最为复杂的机制,本文主要在广义上介绍回收方法,而不拘泥于细节。随着系统的运行,内存会逐渐被消耗,而内存主要占用的部分有两个,分别是高速缓存以及用户进程,内核高速缓存包括磁盘高速缓存,slab缓存等。

首先介绍内核中内存回收的几种方式:

  • 直接内存回收
    当我们调用内存分配函数,比如alloc_pages时,发现此时对应zone管理区中的watermark低于min值,就会触发内存紧缺回收,如果该内存分配的上下文允许休眠,那么会进行休眠,并执行内存回收操作,同时触发周期回收。
  • 内存规整
    内存规整是要先于直接内存回收的,如果内存不足,会用优先进行内存规整,所谓内存规整,就是把使用过的内存迁移到另一个位置,尽量把空闲内存合并成连续的内存,这样减少了内存碎片化,也就能够增加内存分配成功的概率。如果内存规整后依然无法满足内存需求,那就需要进一步执行内存回收了。当然也并不是所有的已用内存页都支持内存迁移,有一些UNMOVEABLE的页是不能被迁移的。目前只有匿名页和page cache支持内存迁移。
  • 周期内存回收(kswapd)
    前面也提到过,当内存低于low watermark,会启动一个线程kswapd,周期性的对内存进行回收,直到内存高于high watermark才停止。为什么需要周期回收,因为有些情况申请内存是不允许休眠等待的,也就不能执行直接回收操作,所以此时内存回收只能交给周期回收。
  • OOM killer
    如果内存规整/内存回收都无法满足系统需要,说明内存已经极度紧缺了,内存耗尽将导致系统无法继续运行下去,所以到此时系统也只能够“断臂求生”了,内核会选择一个process进程(select_bad_process),这个进程是需要满足内核的选择标准才会被选出来,需要注意的是swapper/init和其他内核线程是不允许被kill的。
  • swap交换分区
    交换分区的时候,经常在安装ubuntu或者其他Linux发行版的时候,我们在分区划分时,都会选择一个/swap分区,它就是我们所说的交换分区,当执行内存回收时,有一些特殊的页,比如用户匿名页,他们没有对应的文件可以写回,但是可以被置换一个特定的分区上,从而把内存释放出来被回收使用。交换分区带来一个问题,如果反复执行换入/换出操作,那么带来的系统开销是得不偿失的,因此,和其他文件系统缓存一样,swap分区也实现了对应swap高速缓存机制,因为磁盘操作是比较耗时的,为了防止并发操作引起的问题,在同时申请换出换入时,实际上是在交换缓冲区中取入取出,而没有实际操作磁盘。

内存回收的时机

  • 内存紧缺时回收
  • 周期性回收(kswapd/reap_work)
  • 系统睡眠时回收

反向映射(RMAP)

我们知道对于内存页的回收,根据页的不同,需要做的操作也不同,针对用户态匿名页,内核需要执行的回收操作是把它换出到交换区(swap分区),而针对用户空间映射页,内核需要做的是把该物理页的内容同步到磁盘上文件中。但是除了这些操作后,对于这两种页,内存回收还有一个重要的步骤,那就是需要定位到页page所对应的所有页表项,内核只有把所有的页表项都清除后才能允许对该页被回收重新使用。而反向映射介绍的就是如何根据page来反向的查找到对应的页表,特别是对于共享页,很可能会对应多个页表,通过反向映射,我们需要找到所有的相关页表项。

  • 匿名页反向映射
    匿名页一般是几个进程共享的,比如父进程克隆一个子进程,父子进程共享地址空间和页框,依赖与一个结构体:struct anon_vma.
    include/linux/mm_types.h:
struct page {
...
struct anon_vma *anon_vma;  /* Serialized by page_table_lock */
...
};

根据这个anon_vma可以找到与之匹配的vm_area_struct,再根据此结构找到mm_struct进程管理结构体,可能有多个匹配的进程,进而找到进程对应的用户空间页表,从而找到页表项。

  • 映射页反向映射
    为什么要把映射页的映射和匿名页分开来处理,那是因为系统中的映射页存在的会比较多,最终页表项的匹配是需要扫描进程页表的,如果每个进程都扫描一遍,效率会很低,所以这里更换了另一个算法来提高效率,那就是PST优先搜索树算法来查找物理页page对应的所有页表项。

LRU链表

LRU(Least Recently Used)链表,就是最近最少访问链表,分为active和inactive两种类型的链表,最近访问的page放到active链表,这些不用被回收算法回收,如果长时间未访问的page则放到inactive链表,根据页是否被访问,该page可能在两个链表中移动。在每个memory node结构体中都存在对应的LRU链表结构:

/* Fields commonly accessed by the page reclaim scanner */
spinlock_t      lru_lock;
struct lruvec       lruvec;

对应的lruvec结构体:

struct lruvec {
    struct list_head lists[NR_LRU_LISTS];
    struct zone_reclaim_stat reclaim_stat;
#ifdef CONFIG_MEMCG
    struct zone *zone;
#endif
};
  • 从inactive链表移动到active链表
    对于inactive中的page,会有两次访问才会移动到active链表,如果一次访问后间隔一定时间没有继续访问第二次,那么该page访问次数会被重置。
  • 从active链表移动到inactive链表
    refill_inactive_zone函数是做该项工作的,在内存回收的时候会调用shrink_zone,该函数会调用到refill_inactive_zone函数来填充inactive链表,然后从inactive链表中去回收内存页。因此refill_inactive_zone函数至关重要,
    它的实现决定了哪些page可以被回收。
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python的内存回收机制是一种自动管理内存的技术,它可以检测和回收不再使用的内存空间,以便将其释放给系统。 Python使用引用计数和循环垃圾收集算法来进行内存回收。 引用计数是一种简单而高效的技术,它通过跟踪对象的引用数量来确定是否需要回收内存。当一个对象的引用计数变为零时,内存就会被释放。循环垃圾收集算法用于处理循环引用的情况,即两个或多个对象相互引用,但没有被其他对象引用。循环垃圾收集算法会定期扫描内存,找出这些循环引用并释放它们占用的内存。 了解Python的内存回收机制对于编写高效和可靠的代码非常重要。在使用Python的内存回收机制时,有几个注意事项需要记住:全局变量和循环引用可能会导致内存泄漏。全局变量的引用一直存在,可能会导致对象无法被回收。特别是在循环引用中,全局变量的引用可能导致内存泄漏。应该尽量避免使用不必要的全局变量。另外,大内存对象占用的内存空间较大,可能会导致内存碎片问题。可以考虑手动释放大内存对象的资源,或者使用gc.disable()函数禁用垃圾回收,以减少内存碎片。此外,内存回收机制会带来一定的性能开销。尽管Python的内存回收机制非常高效,但在处理大量数据或对性能敏感的场景中,应该密切关注内存回收的性能影响。 综上所述,Python的内存回收机制使用引用计数和循环垃圾收集算法来自动管理内存。了解内存回收机制可以帮助你编写高效和可靠的代码,并避免内存泄漏问题。注意全局变量和循环引用可能会导致内存泄漏,大内存对象可能导致内存碎片问题,且内存回收机制会带来一定的性能开销。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值