C#.net如何手动释放内存资源

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#.net如何手动释放内存资源相关的知识,希望对你有一定的参考价值。

就像c语言中free(*p);这样。
我现在在学数据结构,想用c#写,但是c#是系统自动释放资源的。
请问如何手动释放资源。
比如删除链表的一个结点,这个过程如何实现。
要求手动释放内存,求代码。

C#中对象的销毁有三种方式Finalize,Dispose,GC
1 public class Foo: IDisposable
2
3 public void Dispose()
4
5 Dispose(true);
6 GC.SuppressFinalize(this);
7
8
9 protected virtual void Dispose(bool disposing)
10
11 if (!m_disposed)
12
13 if (disposing)
14
15 // Release managed resources
16
17
18 // Release unmanaged resources
19
20 m_disposed = true;
21
22
23
24 ~Foo()
25
26 Dispose(false);
27
28
29 private bool m_disposed;
30
31

在.NET的对象中实际上有两个用于释放资源的函数:Dispose和Finalize。Finalize的目的是用于释放非托管的资源,而Dispose是用于释放所有资源,包括托管的和非托管的。

在这个模式中,void Dispose(bool disposing)函数通过一个disposing参数来区别当前是否是被Dispose()调用。如果是被Dispose()调用,那么需要同时释放 托管和非托管的资源。如果是被~Foo()(也就是C#的Finalize())调用了,那么只需要释放非托管的资源即可。

这是因为,Dispose()函数是被其它代码显式调用并要求释放资源的,而Finalize是被GC调用的。在GC调用的时候Foo所引用的其它 托管对象可能还不需要被销毁,并且即使要销毁,也会由GC来调用。因此在Finalize中只需要释放非托管资源即可。另外一方面,由于在 Dispose()中已经释放了托管和非托管的资源,因此在对象被GC回收时再次调用Finalize是没有必要的,所以在Dispose()中调用 GC.SuppressFinalize(this)避免重复调用Finalize。

然而,即使重复调用Finalize和Dispose也是不存在问题的,因为有变量m_disposed的存在,资源只会被释放一次,多余的调用会被忽略过去。

因此,上面的模式保证了:

1、 Finalize只释放非托管资源;

2、 Dispose释放托管和非托管资源;

3、 重复调用Finalize和Dispose是没有问题的;

4、 Finalize和Dispose共享相同的资源释放策略,因此他们之间也是没有冲突的。

在C#中,这个模式需要显式地实现,其中C#的~Foo()函数代表了Finalize()。而在C++/CLI中,这个模式是自动实现的,C++的类析构函数则是不一样的。

按照C++语义,析构函数在超出作用域,或者delete的时候被调用。在Managed C++(即.NET 1.1中的托管C++)中,析构函数相当于CLR中的Finalize()方法,在垃圾收集的时候由GC调用,因此,调用的时机是不明确的。在.NET 2.0的C++/CLI中,析构函数的语义被修改为等价与Dispose()方法,这就隐含了两件事情:

1、 所有的C++/CLI中的CLR类都实现了接口IDisposable,因此在C#中可以用using关键字来访问这个类的实例。

2、 析构函数不再等价于Finalize()了。

对于第一点,这是一件好事,我认为在语义上Dispose()更加接近于C++析构函数。对于第二点,Microsoft进行了一次扩展,做法是引入了“!”函数,如下所示:

1 public ref class Foo
2
3 public:
4 Foo();
5 ~Foo(); // destructor
6 !Foo(); // finalizer
7 ;
8

“!”函数(我实在不知道应该怎么称呼它)取代原来Managed C++中的Finalize()被GC调用。MSDN建议,为了减少代码的重复,可以写这样的代码:

1 ~Foo()
2
3 //释放托管的资源
4 this->!Foo();
5
6
7 !Foo()
8
9 //释放非托管的资源
10
11

对于上面这个类,实际上C++/CLI生成对应的C#代码是这样的:

1 public class Foo

2
3 private void !Foo()
4
5 // 释放非托管的资源
6
7
8 private void ~Foo()
9
10 // 释放托管的资源
11 !Foo();
12
13
14 public Foo()
15
16
17
18 public void Dispose()
19
20 Dispose(true);
21 GC.SuppressFinalize(this);
22
23
24 protected virtual void Dispose(bool disposing)
25
26 if (disposing)
27
28 ~Foo();
29
30 else
31
32 try
33
34 !Foo();
35
36 finally
37
38 base.Finalize();
39
40
41
42
43 protected void Finalize()
44
45 Dispose(false);
46
47
48

由于~Foo()和!Foo()不会被重复调用(至少MS这样认为),因此在这段代码中没有和前面m_disposed相同的变量,但是基本的结构是一样的。

并且,可以看到实际上并不是~Foo()和!Foo()就是Dispose和Finalize,而是C++/CLI编译器生成了两个Dispose 和Finalize函数,并在合适的时候调用它们。C++/CLI其实已经做了很多工作,但是唯一的一个问题就是依赖于用户在~Foo()中调 用!Foo()。

关于资源释放,最后一点需要提的是Close函数。在语义上它和Dispose很类似,按照MSDN的说法,提供这个函数是为了让用户感觉舒服一点,因为对于某些对象,例如文件,用户更加习惯调用Close()。

然而,毕竟这两个函数做的是同一件事情,因此MSDN建议的代码就是:

1 public void Close()

2
3 Dispose(();
4
5
6
这里直接调用不带参数的Dispose函数以获 得和Dispose相同的语义。这样似乎就圆满了,但是从另外一方面说,如果同时提供了Dispose和Close,会给用户带来一些困惑。没有看到代码 细节的前提下,很难知道这两个函数到底有什么区别。因此在.NET的代码设计规范中说,这两个函数实际上只能让用户用一个。因此建议的模式是:

1 public class Foo: IDisposable
2
3 public void Close()
4
5 Dispose();
6
7
8 void IDisposable.Dispose()
9
10 Dispose(true);
11 GC.SuppressFinalize(this);
12
13
14 protected virtual void Dispose(bool disposing)
15
16 // 同前
17
18
19

这里使用了一个所谓的接口显式实现:void IDisposable.Dispose()。这个显式实现只能通过接口来访问,但是不能通过实现类来访问。因此:

1 Foo foo = new Foo();

2
3 foo.Dispose(); // 错误
4 (foo as IDisposable).Dispose(); // 正确
5

----------------------------------以下是CSDN上一位高手的总结----------------------------------------------

1、Finalize方法(C#中是析构函数,以下称析构函数)是用于释放非托管资源的,而托管资源会由GC自动回收。所以,我们也可以这样来区分 托管和非托管资源。所有会由GC自动回收的资源,就是托管的资源,而不能由GC自动回收的资源,就是非托管资源。在我们的类中直接使用非托管资源的情况很 少,所以基本上不用我们写析构函数。

2、大部分的非托管资源会给系统带来很多负面影响,例如数据库连接不被释放就可能导致连接池中的可用数据库连接用尽。文件不关闭会导致其它进程无法读写这个文件等等。

实现模型:
1、由于大多数的非托管资源都要求可以手动释放,所以,我们应该专门为释放非托管资源公开一个方法。实现IDispose接口的Dispose方法是最好的模型,因为C#支持using语句快,可以在离开语句块时自动调用Dispose方法。

2、虽然可以手动释放非托管资源,我们仍然要在析构函数中释放非托管资源,这样才是安全的应用程序。否则如果因为程序员的疏忽忘记了手动释放非托管资源, 那么就会带来灾难性的后果。所以说在析构函数中释放非托管资源,是一种补救的措施,至少对于大多数类来说是如此。

3、由于析构函数的调用将导致GC对对象回收的效率降低,所以如果已经完成了析构函数该干的事情(例如释放非托管资源),就应当使用SuppressFinalize方法告诉GC不需要再执行某个对象的析构函数。

4、析构函数中只能释放非托管资源而不能对任何托管的对象/资源进行操作。因为你无法预测析构函数的运行时机,所以,当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。这样将导致严重的后果。

5、(这是一个规则)如果一个类拥有一个实现了IDispose接口类型的成员,并创建(注意是创建,而不是接收,必须是由类自己创建)它的实例对象,则 这个类也应该实现IDispose接口,并在Dispose方法中调用所有实现了IDispose接口的成员的Dispose方法。
只有这样的才能保证所有实现了IDispose接口的类的对象的Dispose方法能够被调用到,确保可以手动释放任何需要释放的资源。分享给你的朋友吧:人人网新浪微博开心网MSNQQ空间
对我有帮助
8
参考技术A C#用垃圾处理器处理内存,不存在与new运算符对应的运算符。用C#做链表一般是用对象实现链表的节,对象一旦不再使用内存将立即回收,不需要手动释放。可以参见《C#本质论》。 参考技术B C#中内存是自动释放的..通过你的描述,我认为有可能是你写入时使用的变量的存活期很长..你尽量使用局部变量试试.或者你的变量使用完之后使用.dispose()释放掉

最好把写入文件的代码贴上来~
参考技术C 上面的同学的代码都太~~~复杂或理论化了。
一般来说.Net的内存是自动管理的,如果出现了内存占用过多,需要先考虑优化。
确实需要手动回收的话,可以使用 System.GC.Collect() 来回收,CLR会将所有没有被使用的变量空间进行回收。
注意,回收并不一定代表着在任务管理器中的内存占用降低,这是由操作系统的特征决定的。本回答被提问者采纳
参考技术D 对于.net就简单的方法,有close方法的close掉,有dispose的dispose掉,没有的直接null掉.不要用gc

如何手动释放Linux内存的方法

1、首先打开Linux命令窗口,可使用快捷键Ctrl+Alt+T打开。

2、这时查看一下当前Linux系统内存使用情况,使用命令:Free –m,total 内存总数,used 已经使用的内存数,free 空闲的内存数。

3、接下来的操作需要先获取高级用户权限,输入命令:sudo -i,确定后输入高级用户密码。

4、这时进行拷贝文件拷贝,增加内存使用量(即used的占用量),输入命令: cp -r /etc ~/test/。

5、执行命令结束后,再次查看一下当前Linux系统内存使用情况,发现有70M的内存被cached用了。使用命令:Free –m。

6、接下来释放已被占用的缓存,输入命令: cat /proc/sys/vm/drop_caches,回车后返回结果0。

7、接着输入命令:sync,回车后继续输入命令:echo 3 > /proc/sys/vm/drop_caches,回车后继续输入:cat /proc/sys/vm/drop_caches,回车后返回结果3,将/proc/sys/vm/drop_caches值设为3。

8、这样缓存释放就已经完成了,再次执行命令Free –m看看,通过图中可以对比看到,内存被释放了218M。

参考技术A Linux释放内存的命令:
sync
echo 1 > /proc/sys/vm/drop_caches

drop_caches的值可以是0-3之间的数字,代表不同的含义:
0:不释放(系统默认值)
1:释放页缓存
2:释放dentries和inodes
3:释放所有缓存

释放完内存后改回去让系统重新自动分配内存。
echo 0 >/proc/sys/vm/drop_caches

free -m #看内存是否已经释放掉了。

如果我们需要释放所有缓存,就输入下面的命令:
echo 3 > /proc/sys/vm/drop_caches

######### Linux释放内存的相关知识 ###############

在Linux系统下,我们一般不需要去释放内存,因为系统已经将内存管理的很好。但是凡事也有例外,有的时候内存会被缓存占用掉,导致系统使用SWAP空 间影响性能,例如当你在linux下频繁存取文件后,物理内存会很快被用光,当程序结束后,内存不会被正常释放,而是一直作为caching。,此时就需 要执行释放内存(清理缓存)的操作了。

Linux系统的缓存机制是相当先进的,他会针对dentry(用于VFS,加速文件路径名到inode的转换)、Buffer Cache(针对磁盘块的读写)和Page Cache(针对文件inode的读写)进行缓存操作。但是在进行了大量文件操作之后,缓存会把内存资源基本用光。但实际上我们文件操作已经完成,这部分 缓存已经用不到了。这个时候,我们难道只能眼睁睁的看着缓存把内存空间占据掉吗?所以,我们还是有必要来手动进行Linux下释放内存的操作,其实也就是 释放缓存的操作了。/proc是一个虚拟文件系统,我们可以通过对它的读写操作做为与kernel实体间进行通信的一种手段.也就是说可以通过修改 /proc中的文件,来对当前kernel的行为做出调整.那么我们可以通过调整/proc/sys/vm/drop_caches来释放内存。要达到释 放缓存的目的,我们首先需要了解下关键的配置文件/proc/sys/vm/drop_caches。这个文件中记录了缓存释放的参数,默认值为0,也就 是不释放缓存。

一般复制了文件后,可用内存会变少,都被cached占用了,这是linux为了提高文件读取效率的做法:为了提高磁盘存取效率, Linux做了一些精心的设计, 除了对dentry进行缓存(用于VFS,加速文件路径名到inode的转换), 还采取了两种主要Cache方式:Buffer Cache和Page Cache。前者针对磁盘块的读写,后者针对文件inode的读写。这些Cache有效缩短了 I/O系统调用(比如read,write,getdents)的时间。"

释放内存前先使用sync命令做同步,以确保文件系统的完整性,将所有未写的系统缓冲区写到磁盘中,包含已修改的 i-node、已延迟的块 I/O 和读写映射文件。否则在释放缓存的过程中,可能会丢失未保存的文件。

[root@fcbu.com ~]# free -m
total used free shared buffers cached
Mem: 7979 7897 82 0 30 3918
-/ buffers/cache: 3948 4031
Swap: 4996 438 4558

第一行用全局角度描述系统使用的内存状况:
total 内存总数
used 已经使用的内存数,一般情况这个值会比较大,因为这个值包括了cache 应用程序使用的内存
free 空闲的内存数
shared 多个进程共享的内存总额
buffers 缓存,主要用于目录方面,inode值等(ls大目录可看到这个值增加)
cached 缓存,用于已打开的文件

第二行描述应用程序的内存使用:
-buffers/cache 的内存数:used - buffers - cached
buffers/cache 的内存数:free buffers cached
前个值表示-buffers/cache 应用程序使用的内存大小,used减去缓存值
后个值表示 buffers/cache 所有可供应用程序使用的内存大小,free加上缓存值

第三行表示swap的使用:
used 已使用
free 未使用

可用的内存=free memory buffers cached。

为什么free这么小,是否关闭应用后内存没有释放?
但实际上,我们都知道这是因为Linux对内存的管理与Windows不同,free小并不是说内存不够用了,应该看的是free的第二行最后一个值:-/ buffers/cache: 3948 4031 ,这才是系统可用的内存大小。
实际项目中的经验告诉我们,如果因为是应用有像内存泄露、溢出的问题,从swap的使用情况是可以比较快速可以判断的,但free上面反而比较难查看。我觉得既然核心是可以快速清空buffer或cache,但核心并没有这样做(默认值是0),我们不应该随便去改变它。
一般情况下,应用在系统上稳定运行了,free值也会保持在一个稳定值的,虽然看上去可能比较小。当发生内存不足、应用获取不到可用内存、OOM错 误等问题时,还是更应该去分析应用方面的原因,如用户量太大导致内存不足、发生应用内存溢出等情况,否则,清空buffer,强制腾出free的大小,可 能只是把问题给暂时屏蔽了,所以说一般情况下linux都不用经常手动释放内存。本回答被提问者和网友采纳

以上是关于C#.net如何手动释放内存资源的主要内容,如果未能解决你的问题,请参考以下文章

如何手动释放Linux内存?

如何手动释放Linux内存的方法

如何手动释放一个java线程占用的资源

.Net 内存溢出问题怎么才能很好的解决呢?

为啥 io类的资源,在使用完后,需要进行释放

如何手动释放linux内存?