1对象的分配
CLR要求所有的对象都是从托管堆分配。
CLR划出一个地址空间区域作为托管堆。
CLR还要维护一个指针,NextObjPtr。 用来指向下一个对象在堆中分配的位置。
一个区域被非垃圾对象填满,CLR会分配更多的区域。一直重复,直到整个进程的地址空间被填满。所以你的应用程序受进程的虚拟地址空间限制。
32位-1.5GB
64位-8TB。
1.1对象的分配过程
1.计算类型的字段(包括基类继承的字段成员)所需的字节数
2. ***加上对象的开销所需的字节数***。
a类型对象指针(属于哪个类)
b同步块索引 (线程同步所需,垃圾回收使用)
(32位程序,这两个字段各许32位,一共8个字节)
(64位程序,这两个字段各需64位,一共16个字节)
3.CLR检查区域是否够大,如果够大就在NextObjPtr指针指向的地址放入对象。
同时将指针向后移动。
此处有个优化点:CPU从内存中读取数据,是成页读取的,连续分配则能减少CPU读取内存的次数。
如果一直分配的话,可能很快将内存占用满,所以需要GC来回收内存。--垃圾回收
2垃圾回收算法
当new对象的时候,发现没有足够的地址空间来分配对象,则CLR就执行垃圾回收。
事实上是在第0代满的时候就会执行GC。
2.1垃圾回收的算法
根:我们将所有引用类型的变量都称之为 根。
在CLR执行GC的时候,会暂停进程中的所有线程。防止线程在CLR检查期间访问对象并改变其状态。然后CLR进入标记阶段。
标记阶段:
CLR遍历所有对象,将同步块索引中的一位设为0。表示所有对象都应删除。
然后检查所有的活动根,查看他们引用了哪些对象,任何根如果引用了堆上的对象,CLR就会标记这个对象,将同步块索引中的位设为1。
一个对象被标记后,CLR会检查对象中的根,标记他们引用的对象。如果发现对象已经被标记,就不重复检查对象的字段,避免循环引用。
一次垃圾回收过程,标记为0的对象就应该被收回。
OutOfMemeryException异常:当内存耗尽,还要进行new或者进行对象分配时,会抛出该异常。---比如死循环。
内存泄漏
一个常见的原因就是:静态字段引用了集合对象,然后不停的向集合添加数据项。静态字段中对象一直存活,垃圾回收不掉,导致内存泄漏。因此,应尽量避免使用静态字段。
2.2代:提升性能
垃圾回收中的代:0代,1代,2代。GC.MaxGeneration
0代为新生代,2代为最老的代。
新创建的对象都是0代。
CLR会在内存中分配固定大小的代。0代多大,1代多大,2代多大。
过程:
如果0代内存已满,进行GC,标记阶段,不可达的或者说没有引用的标记为0.并从内存中移除
将正在使用的对象标记为1,并转移到1代区域,
同理,CLR会对第1代进行内存分配,当第0代满时,进行GC,0代移动到1代,1代满时,对1代区域进行回收,发现没有可回收对象时,将1代转移到2代,0代转移到1代,0代空出来。
如果第0代全是垃圾的时候,只需要将NextObjPtr指针指向内存头部即可,不必进行垃圾回收。
优化:其实如果垃圾回收了第0代,发现还有许多对象存活,没有多少内存被回收,就会增大0代预算。0,1,2代预算都可以被优化,增大或者缩小预算。
2.3垃圾回收触发条件
出发垃圾回收的条件:
l 代码显示调用GC.Collect()方法
l Windows报告内存过低
l CLR正在卸载AppDomain
l CLR正在关闭
以上都是小对象
2.4大对象
85000字节或者更大的对象。
GC不压缩大对象,因为移动大对象代价过高。
大对象总是在2代,不可能放到0或1代。
大对象一半是大字符串 XML或JSON或者用于IO操作的字节数组(比如从文件或者网络将字节读入缓冲区以便处理)