被栈上直接或者间接引用的对象、本地方法栈中直接或者间接引用的对象、方法区static的变量常量和直接或者间接引用的对象是不能被删除的。原因很简单,他们还有可能或者正在被使用。而和GCRoot没有关联的对象,那他们是可以被删除的。
比如有个标志位,有标记的才清理,不过这有个缺点,那就有内存碎片,因为假设有0x0088这个地方有个1k的对象清理掉了,然后0x9999这个地方也有个1k对象被清理了,我又要new一个2k的对象,那很明显这清理出来的2个1k的空间没法被使用,2k的对象也没法被拆分,这样就不能充分利用内存。
整理就是清除一块区域后,后面的对象要补上来。比如删了007这个位置的,那么007后面的都往前补、移动。缺点:虽然减少内存碎片,但是代价大,所有对象要频繁移动
将内存一分为二,假如我们在1区创建对象,然后对象上有是否删除的标志,然后1区要满了,这时我们必须删除点东西,接下来就把一区里面不需要删除的元素,紧凑地(不是分散的)复制到二区,这样就避免了内存碎片,也适当控制了开销,缺点是分区就是代表需要两倍的内存。
图如下:
注意这里都是思路而非实际,实际中标志的往往是不删除的,不标志的反而是删除的,不过具体还得具体分析,主要理解思想,而且具体实现肯定多样而复杂。
分为年轻代(young)和老年代(old)。
一个Eden区,两个Survivor区(一般情况)。大部分对象是在Eden区中生成。当Eden区满了,会触发young区的GC(采取复制算法),还存活的对象将被复制到Survivor区中(两个中的一个)。
当这个Survivor也满了,此区还存活的对象将被复制到另一个Survivor中,不过实际操作中他们是交替进行的,比如先对E和S1进行删除操作,留下来的复制到s0,然后后来又对E和S0进行删除操作,复制到S1区,如此反复,这样比单纯一分为二效率高,也和对象“死”的快有关。
在过程中,两个Survivor都满了,还存活的对象会被复制到老年代。
PS:为什么E区比较大呢?因为实际中大部分对象都是“朝暮而亡”就是一般很快就被清理了,只有小部分会长期存在,这个s:s:e的比例大概是1:1:8
每次YoungGC触发,存活下来的对象年龄就加一,到一定年龄后会晋升到老年代,那这个一定年龄不是一直固定的:
15是最大值,7是初始值。晋升的岁数是个动态变化的值,在每次gc的时候都会重新计算,这和s区存活率(默认50%)等其他参数有关。MaxTenuringThreshold在不同的垃圾收集器里的默认值也是不同的,对Parallel Scavenge来说这个值默认是15,对G1来说这个值默认是15,对CMS来说这个值默认是6.
至于为什么要去老年代很简单,都说对象死得快,但是你七八次都不死,那很有可能就会长期存在,七八十次都死不了,所以干脆拿到old区维护,省得在young区复制来复制去。
old区还会存一些很大的对象,因为大对象复制开销过大了,比如一百万长的数组,那不会在E区存在,这个数组会直接存到old区。
old放满后会触发oldGC,顺带也肯定会引起youngGC,所以又称fullGC,从而引起stop the world,就是java程序暂停,全力进行垃圾回收,这里回收一般采取标记或者标记整理类型的算法。
ParNew 是比较有名的youngc清理器,cmc则是老年代的
现在jdk新版叫G1,理念又有很大不同哦