该计数法使用计数器来活对象和不再使用对象,大概就是给堆中的每个对象分配一个计数器,给该对象赋值后计数器加一,当该对象出了作用域后减一。一旦引用计数器为0时,就满足了GC条件。引用计数法有一个致命的缺陷,对循环引用的对象无法回收。
该策略执行原理是,当堆中有效内存空间被耗尽时,会停止程序,然后进行两项工作,标记和清除。标记的过程即遍历所有的GC Roots,所有可达的标记为活对象。清除即把没有活对象的标记的全部清除。该策略缺点显而易见:
a>效率低,需要遍历所有的GC Roots,而且在GC的时候需要停止程序。
b>标记清除出来的内存空间不是连续的,JVM需要消耗性能去维护这个不连续的内存空间,在下一次分配数组对象内存的时候,去找连续的内存需要消耗额外的性能。
复制算法讲内存分为两个区间,活动空间和空闲空间,当有效内存空间被耗尽时,会暂停程序,把活动空间中的存活对象复制到空闲空间,并且严格按照内存地址依次排序,并把存活对象指向新的内存地址,此时情况活动空间的对象,空闲空间变为活动空间,活动空间变为空闲空间,然后启动暂停的程序。可以看出该算法弥补了标记清楚的弊端,但是是以多一部分的内存空间为代价。
对象大概可以分为三大类,新生代,年老代,永生代,新生代即存活周期很短,创建不久就销毁的对象,比如循环中的变量;年老代,创建后有可能会被销毁,比如单例对象,数据库链接对象;永生代,一旦创建永生不灭(夸张一点),比如spring 中加载的类信息。
其中,新生代和年老代在Java 堆中,而永生代在方法区中,这里提一下,在JDK1.8后,方法区的内存与操作系统内存共享。由于永生代的存活周期太长,所以分代搜索算法针对新生代和年老代对象。
这类对象存活时间周期都很短,所以这类对象最适合复制算法。但是有一个问题,将会耗费50%的内存资源,其实并不会耗费50%,因为新生代对象的存活率较低,一般的使用两块10%的的内存作为空闲和活动空间,另外的80%内存则是用来给新建对象分配内存的。一旦发生GC,降10%的活动区间与另外的80%的存活对象转移到空闲区间,剩下90%的内存全部释放,再开辟10%的空闲内存,依次循环。使用这样的方式,我们只浪费了10%的内存,这个是可以接受的,因为我们换来了内存的整齐排列与GC速度,这个策略的前提是,每次存活的对象占用的内存不能超过这10%的大小,一旦超过,多出的对象将无法复制。
这类对象存活率较高,而且都是从新生代转过来的,经历了多次GC,就像人,获得久了就成老年人了。新生代转到老年代有两种情况:
a>新生代的每个对象都会有一个年龄,对应着经历GC的次数,每经历一次CG,就加一,当年龄到达一定程度时,转为老年代。而这个条件值是可以在JVM中设置的。
b>新生代的存活对象超过10%时,多余的对象就会被转到年老代,这时候年老代就是新生代的备用仓库。
这类对象存活率太高,而且不能使用复制算法,因为它没有备用备用仓库。因此针对永生代一般使用标记清除或标记整理算法。
原文:https://www.cnblogs.com/rolayblog/p/11387546.html