对象代龄
代龄是旨在提高垃圾收集器性能的一种机制。有以下几点:
- 对象越新,其生存期越短;
- 对象越老,其生存期越长;
- 对托管堆的一部分执行垃圾收集要比对整个托管堆执行垃圾收集速度要快。
在托管堆初始化时,其中不包含任何对象。这时添加到托管堆中的对象被称为第0代对象。第0代对象就是新构造的对象,垃圾收集器还没有对他们执行过任何的检查,加入新启动一个应用程序托管堆,分配了5个对象(A到E),经过一段时间后,对象C和E将变为不可达对象。
当CLR初始化时,它会为第0代对象选择一个阙值,假定为256KB。(实际可能会与此不同)当分配新对象导致第0代对象超过了为其设定的阙值时,垃圾收集器就必须启动了。假定上面的A到E总共占用了256KB,那么当F开始被分配时,垃圾收集器就会启。垃圾收集器判定对象C和E为垃圾,因此会压缩对象D使其邻接于对象B。此次垃圾收集器中存活下来的对象(对象A,B,D)将被认为是第1代对象。经过一轮垃圾收集的检查加强的存活下来并且光荣的升官加爵。
在第一次垃圾收集执行后,0代对象暂时空缺。但很快,随着应用程序的运行,又有新的对象被分配而成为第0代对象。新加入的F到K就变为了0代对象变为了准炮灰。随着应用程序的运行,对象B(一代长老),H(0)和J(0)又成为不可达对象,它们的内存也要在某一点被回收。
现在假设应用程序又试图分配对象L,这将在一次使第0代对象超过它的阙值容量,于是又必须开始执行第二次垃圾收集。在这前面,垃圾收集器必须要判定要收集那些代的对象。垃圾收集器期会为0代对象选择一个阙值(大约256KB),其实也会为第1代对象选择一个阙值容量。假设第1代对象的阙值容量为2MB。
当第二次垃圾收开始执行时,它也会查看第1代对象占用了多少内存,在本例中,由于第一代对象(A,B,D)占用内存远少于2MB,所以垃圾收集器只会去检查第0代对象。由于新创建的对象的生存期比较短,所以第0代对象成为垃圾对象的数量会比较多,因此对第0代对象执行垃圾收集将有可能比较多的内存,忽略掉第1代对象提高垃圾收集的速度。
忽略第1代对象的意义不仅仅在于不对它们执行垃圾收集。实际上,更重要的是垃圾收集器不用再遍历整个托管堆中的对象了,可以提高垃圾收集器的性能。说忽略代龄较大的对象的一些内部引用,而不是全部内部引用是因为代龄较大的对象有可能引用一个新的对象。为了确保这些代龄较大的对象中的一些新对象也能被检测到,垃圾收集器使用JIT编译器内部的一种机制来在对象的内部引用字段改变时设置一个相应的为标记。这使得垃圾收集器可以知道自从上次垃圾收集执行后以来,那些代龄较大的对象已经被应用程序所改写。这样垃圾收集器只需检查它们是否引用了第0代对象就可以了。
生存期比较长的对象将趋向于继续存活,所以第一代对象继续成为可达对象的可能性比较大。所以第一代对象继续成为可达对象的可能性比较大。因此垃圾收集器检查第一代对象,很有可能找不到很多垃圾,能够回收的内存也就有限,这样收集第一代很有可能浪费大量的时间。如果垃圾对象位于第一代,那么它仍然健在。第二次垃圾收集后。托管堆中存在
A--B---D---F--G---I---K(均为一代长老了)
第0代对象中的幸存者有升官加爵了变为1代,因为垃圾收集器没有检查第一代,B内存没有被收集。虽然已经成为不可达对象。但是由于没有经历洗礼,所以没有升官加爵;
此时0代为空,新的对象继续加入成为了0代长老(L到O),过一段时间,G、L、M又成为不可达对象。此时托管堆如下:
一代A---B---D---F---G---I---K 0代L---M---N---O
此时1代中堆着N多的不可达垃圾包括B。
此时对象P的加入又是0代超标了,继续垃圾收集。因为1代的容量2MB,没有超标,继续不对1代收集(B,G为1代中的蛀虫);本次垃圾收集完毕后,托管堆如下:
一代A---B---D---F---G---I---K---N----O
0带被适时杀死,一代里面的蛀虫继续增长,没有控制是不行的,结果一代的容量达到了其阙值2MB,此时应用程序分配P到S对象,这时0代由满了。此时托管堆的分配情况:
一代A---B---D---F---G---I---K---N----O 0代P----Q----R----S
此时应用程序试图分配对象T,当然是没有地方了,0,1代都满了,需要清理了,这时发现一代对象的内存容量已经达到了2MB,然而一代中存在着大量幸存下来的垃圾。本次垃圾收集器是公平的,同时收集第0代和第1代中所有的垃圾对象,痛苦,所以的垃圾一扫而光了,此时托管堆为:
二代D---F---I----N----O 二代Q-----S(0带空了)
和前面一样,本次垃圾收集完毕后,幸存下来的将被提升为1代,而一代中存活下来的被荣升为2代了,0代为空了,在产生第二代对象之前,系统有可能已经执行了多次垃圾收集,但只有在第一代对象的内存总量达到其阙值时,垃圾收集器才会检查一代对象,而此时系统可能已经对0代对象执行了N多次垃圾收集了。
CLR托管堆只支持3个代龄,0,1,2,CLR初始化时都会为其设置阙值容量(大约为,256KB,2MB,10MB),
CLR垃圾收集器是一个自调结的玩意,他会根据对象的状态,变为不可达的频率自适应的调节阙值。实在是高啊。
CLR的垃圾收集原理其实也给人一人生道理,要想活得更长,就需向更高一层走,而要想往更高一层走就要经过残酷的洗礼。剩者为王。
- 屠刀总是先指向弱者,因为强者数大根深啊,杀一次得不偿失
- 强者拥有更多的资源和更多的容忍
- 再强也不要超过别人给的限度,否则权利是人家给的,人家不高兴了随时可以拿回去