由于目前收集器都采用分代算法,因此Java堆可以分为新生代和老年代。
分代回收原理:对象首先被分配在新生代,经历过垃圾回收后若仍存活,则移到老年代。但这样老年代很快会被填满,导致触发Major GC,性能受影响。
于是将新生代分为Eden区和Survivor区,Eden满时触发Minor GC,将存活的对线移至Survivor区。这样直到对象经历过一定次数的回收仍存活时,才将其移到老年代,这样就减少了Major GC(可视为Full GC)。
那为什么要设置俩Survivor区?答案是为了解决内存碎片问题。
单一Survivor区:
双Survivor区(保证有一个Survivor区为空,实现连续存储):
对于需要大量连续内存空间对象(如数组),为避免频繁复制导致效率降低,直接将其分配到老年代。
给对象添加一个引用计数器,为0时则可回收。
简单、高效,但难以解决对象间循环引用的问题,因此不被使用。
以GC Roots对象为起点,向下搜索引用链。若一个对象与GC Roots没有任何引用路径,则判定为可回收。
对象被判断为不可达后,会被第一次标记,同时进行筛选:
①对象重写了finalize
方法 ②对象的finalize
方法未被虚拟机调用过
当满足任一条件,则执行finalize
方法并放入队列进行二次标记,这之后才会被真正回收。
最普遍的引用。
内存不足时,JVM也不会回收强引用对象,只会抛出OutOfMemoryError。
内存够用时,JVM不会回收;内存不足时,JVM回收软引用对象。
垃圾回收器一旦发现只有弱引用的对象,就会进行回收。
不过垃圾回收线程的优先级很低,不一定很快能发现这些对象。
主要用来跟踪对象被垃圾回收的活动,必须和引用队列联合使用。
后两个很少使用,一般通过弱引用来加速垃圾回收速度,避免内存溢出。
标记不能回收的对象,然后同一回收掉所有没标记的对象。
缺点:①效率不高 ②内存碎片问题
将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
标记后,不是直接回收未标记对象,而是将标记对象向一端移动,然后清理掉边界外的内存。
新生代中,每次收集都有大量对象被回收,于是选用“标记-复制”算法,复制少量存活对象即可完成回收。
老年代中,对象存活率较高,且没有额外空间进行分配担保,于是使用“标记-整理”算法进行回收。
单线程进行收集,并且必须暂停其他所有工作线程。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Serial收集器的多线程版本。
JDK1.8 默认收集器,关注CPU吞吐量,提供了很多参数供用户找到最合适的停顿时间或最大吞吐量。
使用标记-清除算法,关注用户线程的停顿时间。
面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器,以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征。
使用Region划分内存空间,根据优先级来进行区域回收。