谈谈自己对内存回收的理解

内存回收,是内核在内存不足的情况下,一种释放内存的方法,在4.4.198内核里,主要有两个函数涉及内存回收,一个是kswapd,一个是drop_cache;
先说说kswapd,这是一个内核线程,每个内存节点都有一个这样的内核线程,名字是kswapd%d;在alloc_page函数中用低水位分配内存失败后,进入慢速路径的第一件事情

static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
						struct alloc_context *ac)
{
	..............
	if (gfp_mask & __GFP_KSWAPD_RECLAIM)
		wake_all_kswapds(order, ac);
	..............
`}

我们知道,内核线程的函数类型是int (*fun)(void *),那么给kswapd的传参是什么呢?

int kswapd_run(int nid)
{
	pg_data_t *pgdat = NODE_DATA(nid);
	int ret = 0;

	if (pgdat->kswapd)
		return 0;

	pgdat->kswapd = kthread_run(kswapd, pgdat, "kswapd%d", nid);

嗯,把pgdat传给了kswapd线程 ,kswapd主要用到了pg_data_t结构体的两个成员

typedef struct pglist_data {
	...
	int kswapd_max_order;
	enum zone_type classzone_idx;
	...
} pg_data_t;

这两个成员,一个是alloc_page时传入的order值,一个是第一个最适合分配的zone序号。kswpad内核线程会根据这两个参数,去判断现在是否要继续进行内存回收,详情见pgdat_balanced函数,kswapd函数要回收到内存高水位之上才停止回收动作也在这个函数里面体现出来了。
有意思的是,swapd的内存回收方向(假设我的gfp_mask是GFP_HIGHUSER_MOVABLE),是从zone_normal开始,到zone_highmem结束,而alloc_page函数是从zone_highmem开始,到zone_normal结束,方向是相反的,这样做的好处是,可以减少获取锁的冲突;试想,如果两者方向相同,那必然导致争抢同一把锁的概率大大提升。
kswapd主要使用三个手段去回收清理内存空间:

  1. 回收lru链表,具体请查看shrink_lruvec函数
  2. 调用shrink_slab从而调用注册进入系统的shrinker以回收内存
  3. 可能会调用compact_pgdat进行内存规整操作

看到后面就会知道,kswapd实现内存回收的方法和drop_cache是不一样的。
kswapd的实现,这里就不讲解了,网上已经有非常多写的很好的文章,主要过程就是,要么让出CPU进行睡眠;要么从ZONE_HIGHMEM->ZONE_NORMAL(依然假设我的gfp_mask是GFP_HIGHUSER_MOVABLE)找到第一个不平衡的zone,然后从ZONE_NORMAL一直进行内存回收直到这个zone,内存回收的操作有shrink_lruvec和shrink_slab,对每个zone进行了内存回收的操作后,可能会调用compact_pgdat进行一个异步的内存规整操作。

下面来聊聊drop_cache,具体实现的函数是drop_caches_sysctl_handler,他能够接收的参数是1-4,嗯,4是个神奇的参数,我之前一直以为只是能传1-3,具体4是干嘛用的,自己看代码吧。

int drop_caches_sysctl_handler(struct ctl_table *table, int write,
	void __user *buffer, size_t *length, loff_t *ppos)
{
	int ret;

	ret = proc_dointvec_minmax(table, write, buffer, length, ppos);
	if (ret)
		return ret;
	if (write) {
		static int stfu;

		if (sysctl_drop_caches & 1) {
			iterate_supers(drop_pagecache_sb, NULL);
			count_vm_event(DROP_PAGECACHE);
		}
		if (sysctl_drop_caches & 2) {
			drop_slab();
			count_vm_event(DROP_SLAB);
		}
		if (!stfu) {
			pr_info("%s (%d): drop_caches: %d\n",
				current->comm, task_pid_nr(current),
				sysctl_drop_caches);
		}
		stfu |= sysctl_drop_caches & 4;
	}
	return 0;
}

我们知道,当我们给drop_cache传入参数1时,释放的是pagecache,那么,具体是怎么释放的呢?内核是这样做的,遍历每个super_block实例,每个super_block实例又有一个链表把该super_block实例下的inode链在了一起,然后,遍历每个inode,每个inode有成员变量i_mapping指向该inode的address_space,address_space里面有一颗基数树,用于存放属于这个inode的pagecache,内核通过这个方法找到了所有pagecache,然后把可以释放的pagecache就给释放掉了;与kswapd线程对比,这个方法显然暴力了很多,毕竟通过lru链表进行释放,至少是用的两次机会法,不见得一次性就把这些pagecache给回收回来。
实现的代码也很清晰

static void drop_pagecache_sb(struct super_block *sb, void *unused)
{
	struct inode *inode, *toput_inode = NULL;

	spin_lock(&sb->s_inode_list_lock);
	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
		spin_lock(&inode->i_lock);
		/*
		 * We must skip inodes in unusual state. We may also skip
		 * inodes without pages but we deliberately won't in case
		 * we need to reschedule to avoid softlockups.
		 */
		if ((inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) ||
		    (inode->i_mapping->nrpages == 0 && !need_resched())) {
			spin_unlock(&inode->i_lock);
			continue;
		}
		__iget(inode);
		spin_unlock(&inode->i_lock);
		spin_unlock(&sb->s_inode_list_lock);

		cond_resched();
		invalidate_mapping_pages(inode->i_mapping, 0, -1);
		iput(toput_inode);
		toput_inode = inode;

		spin_lock(&sb->s_inode_list_lock);
	}
	spin_unlock(&sb->s_inode_list_lock);
	iput(toput_inode);
}

当我们给drop_cache传入参数2时,内核实际上就调用了注册进来的shrinker,调一把回调,就把内存释放了。值得一提的是,对于inode_cache和dentry_cache,都向系统注册了shrinker,这时候就会把inode_cache和dentry_cache给清理了。

ThreadLocal是Java中的一个类,用于实现线程本地变量。它的作用是为每个线程提供一个独立的变量副本,使得每个线程都可以独立地操作自己的变量副本,而不会影响其他线程的副本。\[1\] ThreadLocal的实现原理是通过在每个线程中维护一个ThreadLocalMap对象来存储变量副本。每个ThreadLocal对象作为key,对应的变量副本作为value,存储在当前线程的ThreadLocalMap中。这样,不同线程之间的变量副本是相互隔离的,每个线程只能访问自己的变量副本。\[2\] 当我们使用ThreadLocal的set方法设置变量值时,实际上是将值存储在当前线程的ThreadLocalMap中,而使用get方法获取变量值时,会先获取当前线程对象,然后使用这个线程对象去访问ThreadLocalMap中的数据,从而获取到对应的变量副本。\[2\] ThreadLocal的使用场景包括但不限于以下几种情况: 1. 在多线程环境下,需要为每个线程维护独立的变量副本,避免线程安全问题。 2. 在某些情况下,需要将一些数据在方法调用链中传递,而不希望在每个方法中都显式传递参数。 3. 在Web应用中,可以将一些需要在同一请求中共享的数据存储在ThreadLocal中,避免使用全局变量或者在方法间传递参数的方式。 需要注意的是,使用ThreadLocal时要注意内存泄漏的问题。由于ThreadLocalMap中的Entry对象是使用ThreadLocal作为key的弱引用,如果ThreadLocal没有被外部引用,那么在垃圾回收时,ThreadLocal可能会被回收,但是对应的变量副本却无法被回收,从而导致内存泄漏。因此,在使用完ThreadLocal后,应该及时调用remove方法将其从ThreadLocalMap中移除。\[3\] #### 引用[.reference_title] - *1* *2* *3* [【Java面试】谈一谈你对ThreadLocal的理解](https://blog.csdn.net/Zhangsama1/article/details/128215901)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值