C#内存管理与垃圾回收

摘要:
垃圾收集必须从根开始,就像生孩子一样。在垃圾收集开始之前比C更快。通过这种方式,垃圾收集器线性扫描线程堆栈,递归扫描托管堆,最后标记托管堆中的所有引用对象。未标记的对象是垃圾,等待收集。CLR内存被程序占用。有关内存管理和垃圾收集的更多信息,请等待我的下一篇博客。

垃圾回收还得从根说起,就像生儿育女一样。

:根是一个位置,存放一个指针,该指针指向托管堆中的一个对象,或是一个空指针不指向任何对象,即为null。根存在线程栈或托管堆中,大部分的跟都在线程栈上,因为定义的变量就存在线程栈上,类型对象指针存在托管堆中,因为实例化一个对象要额外分配两个字段“类型对象指针”和“同步块索引”。

 

类型对象指针的作用。实例化一个对象并没有为其方法分配内存,类型的静态字段分配内存,而实例要向调用属于类型的一些东西,就必须通过类型对象指针。如对象的实例是共用类型的方法,实例只需要通过类型对象指针调用类型的方法,更多关于方法的调用请看我的这篇博客

 

同步块索引的作用。1:用于lock,使对象在同一时刻只能一个线程访问;2:用于获取对象的hashCode;3:在垃圾回收时标志某个对象是否是垃圾。关于lock最经典的一个例子就是单例了,大家的实现都是实例化一个object对象,然后锁住它,然后在判断是否要实例要实现单例的那个对象。我们为什么要实例化一个object,而不是直接lock(typeof(object)),那是因为这样会把object这个类型给锁住,锁住期间,任何使用线程使用lock(typeof(object))就必须等待,object还是可以正常使用。lock能起到单线程访问的原因是:它里面有一个空的for死循环,一直在读同步块索引中的一个位,如果这个位没有被标志跳出循环,如果被标志就一直执行循环,直到方法执行完成,其他线程就一直等待,现在你知道lock能使你的程序只能单线程反问也知道lock的效率低了吧。

 

NextObjPtr一个最牛B的指针。CLR中的所有资源都从托管堆中分配,托管堆是一块连续的内存空间,维护一个指针NextObjPtr,它指向上一个对象地址的后面,下一个对象的开始位置,若托管堆中没有对象就指向托管堆的开始位置,每分配一个对象就将NextObjPtr指向这个对象的后面,以准备开始分配下一个对象。NextObjptr指针移动的位置其实就是上一个对象所在空间的长度,从指向对象的开始位置改为对象的末尾吗。从哪里开始分配对象就全靠NextObjPtr啦。

 

实例化一个对象需要多少空间?对象的所有字段所需的内存+类型对象指针+同步块索引。关于类型对象指针和同步块索引的作用前面已经提过了。有些字段没有明显定义,但它确确实实存在,每个对象除了object的对象都有base字段,通过它可以调用父类的实例字段和方法,通过它你可以访问你爷爷的爷爷定义的字段和方法。CLR用递归的方式调用父类的方法,当然也要看,你爷爷是否愿意让你调用,原因你懂的。

 

在垃圾回收开始之前速度比C。对象就这样开心的在托管堆中分配,托管堆的容量是有限的,总有一天第0代会满,容不下一粒沙子。垃圾回收就出场了,在垃圾回收出场之前,你使用内存很happy,当然速度是非常快,比C语言的速度还快,因为C的内存是随便分配,只要找到合适大小的区域,就在那里分配内存了,这样会导致内存碎片,有时需要一块大的内存,需要遍历多处。垃圾回收的时候日子就不是那么好过了。速度肯定比C慢了,看下面你就知道垃圾回收的时候,程序的速度为什么慢了。

 

垃圾回收分两步:1:标记;2:压缩

1:标记。在垃圾回收开始的时候,垃圾回收器视托管堆中的所有对象都为垃圾,即线程栈上没有指针指向托管堆。这样的估计是因为一个对象被视为垃圾就是它没有被引用,当垃圾回收开始的时候,垃圾回收器会沿着线程栈线性扫描,当线程栈上的一个变量引用了托管堆中的对象时,垃圾回收器就会将这个对象标记,即修改该对象同步块索引中的一个特定的位,同步块索引就是一个bit数组,每一个元素都有它特定的作用,上面就列出了我所知道的三个功能。被标记的对象也可能引用其他的对象,而被引用的对象同样会被标记,垃圾回收器是用递归的方式将这些对象一一标记的,一个对象可能会被多个对象引用,当垃圾回收器发现某个对象被标记时就会退出递归,因为再往下递归完全是多余,而且还可能出现死循环。

垃圾回收器就这样线性的扫描线程栈,递归的扫描托管堆,最后将托管堆中所有被引用的对象标记,而没有被标记的对象就是垃圾,等着被回收。

 

2:压缩。当垃圾被回收之后,就会出现磁盘碎片,那么就要对托管堆进行整理,即压缩。将没有被回收的对象放在一起,靠近托管堆开始的位置,将剩余的内存腾出空间来以便存放新的对象。由于压缩很多对象就会移动位置,而引用他们的指针都会变得无效,所以托管堆要修改所有指针的指向,以保证不会因为垃圾回收而让对象变得不可到达,指针变得无效。

压缩完了之后,又腾出了空间,又可以分配新的对象,当第0代满了之后又进行垃圾回收,垃圾回收就这样一直进行着,直到回收了3代还是没有内存可以分配,那就是弹尽粮绝的时候了,CLR会告诉你OutOfMemoryException。CLR的内存被的程序吃光了。更多关于代的信息,可以看我的这篇博客。在第0代满的时候就会进行垃圾回收,第0代回收完之后还是没有足够的内存存放当前对象就回收第1代,如果还是不够就回收第2代,够就不回收下一代,垃圾回收还可以用代码控制GC.Collect()。

更多关于内存管理和垃圾回收的内容,请等待我的下一篇博客。

 

作者:陈太汉

博客:http://www.cnblogs.com/hlxs/

免责声明:文章转载自《C#内存管理与垃圾回收》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Unity2019使用Android Studio 4出安卓包幸运拼系统代码幸运拼团系统源码分享下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

google代码风格(转)

Google C++ 风格指南 - 中文版 from http://code.google.com/p/google-styleguide/ 版本: 3.133 原作者: Benjy Weinberger Craig Silverstein Gregory Eitzmann Mark Mentovai Tashana Landray 翻译: Yul...

为何将未被初始化的Integer变量赋值给int变量时会抛出空指针异常?

Integer的NullPointerException 学习包装类的时候看到这样一个问题: public class TestBox { Integer i; int j; public void go(){ j = i;//第七行 System.out.println(j);...

Linux系统调优

Linux核心参数都是放置在/proc下面;系统的参数都是放置在/proc/sys swap最好放置在运行最快的硬盘上面,但是swap并能取代ram,因为并有I/O上面的损耗,所以优先考虑检验内存没有泄露以及增加内存提高性能;另外swap退而求其次最好能够在一个单独的分区上面,或者是拥有多个swap分区,这样可以让linux系统能够多线程并行写到硬盘上...

Linux内存描述之内存节点node--Linux内存管理(二)

1 内存节点node 1.1 为什么要用node来描述内存 这点前面是说的很明白了, NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快 Linux适用于各种不同的体系结构, 而不同体系结构在内存管理方面的差别很大. 因此linux内核...

Delphi7中编译提示“Unsafe type 'PChar'”的原因及处理办法

delphi7中加入了对.net的支持 在.net中是没有指针的(托管环境中),所以指针都是不安全的,不符合.net规范 所以d7里有警告,可以不管它 DELPHI7已经考虑到了移植到点NET的问题,至于为什么有的人遇到,有的人没有遇到,那是因为各人的编译选项不同。在Project菜单下选Options“Compiler Messages”,最下面三个选项...

c++ 动态判断基类指针指向的子类类型(typeid)

我们在程序中定义了一个基类,该基类有n个子类,为了方便,我们经常定义一个基类的指针数组,数组中的每一项指向都指向一个子类,那么在程序中我们如何判断这些基类指针是指向哪个子类呢? 本文提供了两种方法 (1) 自定义类id, (2)typeid 一、自定义id 如下所示基类father有两个子类son1 和 son2,我们在基类中定义类虚函数id,子类中分别重...