堆区是用来存储new出来的对象的,当对象填充满堆区后,就会导致内存爆掉,程序就GG了。
就需要科学的进行GC:
首先需要判断这个对象是否应该被删除,如果应该被删除,那么需要将这个对象清理掉。
判断的标准:GCRoot(一般是指被栈上的直接或间接引用、本地方法栈直接或间接引用的对象、方法区的j静态static变量或常量直接或间接引用的对象)
和GCRoot没有相连的关系的就可以删除。
清理堆区对象的思路:
- 标记-清理(对象后打标,如果应该被删除就标记一下,然后再扫描一次,把含有标记的对象删除掉)
但是标记清理算法的缺点是产生内存碎片。【就像衣服打洞】
- 标记-整理(清除过后,后面的对象补上来)
这样就减少了内存的碎片,但是代价太大,所有对象都需要前移。
- 复制算法:
复制算法将内存1分为2.在1区上创建的对象标记是否需要删除,等到快满的时候,不是直接将这个对象删除,而是往2区进行复制,
需要删除的就不复制过来了,不需要删除的这些就紧凑的复制过来。这样就避免了内存碎片问题和开销也不大。
但是其缺点就是需要2倍的内存。
实际GC过程:
将堆区进行划分为:年轻代、老年代。
对于年轻代又进行划分为3个区:E区、S(surive)0区、S1区。
而老年代就只有1个区。
整个过程在进行new对象的时候它其实是产生在E区的。当E区快满了的时候,就会触发Young区的GC,采用的是复制算法。会对需要删除的对象上打标记,不需要删除的对象依次复制到S0区。
这里有2点疑问:
- E区比S区大
- 有2个S区
E区比S区大的原因是对象都有个特点:“朝生夕死”,很容易就夭折了,幸存下来的比较少。比例大概是1:1:8
需要2块S区是因为需要交替工作的,即E区打完标记幸存后放入S0区,然后需要将E区和S1区删除,然后等下一次E区快满的时候将S0区、E区所有对象进行打标全部复制到S1区。S0和S1交替使用作为幸存下来的区域。
即E+S1复制到S0、E+S0复制到S1、E+S1复制到S0。。。。。反复执行。
比复制算法直接1分为2内存的利用率高点。
然后再看old区:
其实每次Young GC这个对象的年龄就会+1,即如果在这次GC后这个对象活下来了其年龄就+1.
如果age=15,就不再往S区复制了而是直接到Old区。
Old区除了存了年龄》=15岁的对象还存储“大对象”(大对象的复制消耗比较大,所以直接存到old区)
如果Old区快满的时候也会进行GC,Old GC一般会伴随Young GC,所以Old GC又叫Full GC,会引起整个java程序暂停然后全力的进行垃圾回收,通过标记清理或标记整理的算法。
总结:标记清理或标记整理主要用于Full GC,复制算法主要用于Young GC.
年轻代中比较有名的收集器是ParNew,老年代中比较有名的垃圾收集器是CMS。
最新版的JDK不再建议用以前的垃圾收集器了,而是采用全新的G1垃圾收集器