JVM的垃圾回收机制。
一.分代
1.将堆分代,分为年轻代、老年代,分代的目的是将容易死的和不容易死的区别开来,以提高GC的效率。再一个就是很多对象都死的很快,所以把new出来的放到年轻代可以高效地回收大量内存空间。
2.较大的对象可以直接放到老年代。
3.年轻代分为两部分,eden和survivor,survivor又分为了from和to两部分,它们的大小默认是8:1:1。eden区满了之后会将新对象放到survivor,from区满了会将没有满15次minor GC的对象放到to,满了的放到老年代。老年代满了会触发full GC,然后接下来就是报内存溢出错误。
4.年轻代的GC被称为 minor GC,老年代的GC被称为 major GC,针对整个堆的GC叫 full GC。
5.full GC的触发条件是
老年代满了。
调用了System.GC。
预计年轻代转移至老年代的需求内存空间大小大于老年代剩余内存空间(悲观算法)。
(还有持久代满了,持久代本来是在堆上的,它被用来实现方法区,但jdk1.8使用了元空间替代了持久代,方法区被移动到了本地内存上,不再占用JVM的堆的内存空间)
6.垃圾回收算法。
① 引用标记:每个对象被引用了多少次进行计数,解除了引用就计数-1,归零后被GC回收。但无法解决两个对象相互引用的问题。
②复制: 将GCRoot和其所能到达的对象从from复制到to上,然后将form和to引用互换,保持to是空的。但这种方法需要暂停所有线程,并且需要两倍的内存空间。
③标记清除 : 从GCRoot开始遍历并标记活对象,然后再遍历整个堆,清除没有标记的对象。但这种方法需要暂停所有线程,并且会产生内存碎片。
④标记整理: 从GCRoot开始遍历并标记活对象,再将活对象全都按顺序放到堆里的其中一块上。可以避免内存碎片和空间问题。
⑤分代算法 : 对不同的区用不同的算法。
⑥自适应: 有的垃圾收集器(基于Adaptive算法的垃圾回收器)还会根据特定情况使用不同算法。
7.垃圾回收器。
年轻代----------------
Serial (复制算法的单线程收集器,使用户线程全部暂停)
Parallel Scavenge(复制算法的偏向增加CPU吞吐率(用户线程时间/用户时间+垃圾回收时间)的多线程收集器,使用户线程全部暂停)
ParNew(复制算法偏向减少暂停时间的多线程收集器,使用户线程全部暂停)
老年代---------------
Serial Old(标记整理算法的单线程收集器,使用户线程全部暂停)
Parallel Old(标记整理算法的多线程收集器,使用户线程全部暂停)
CMS(标记清除算法的收集器,分为以下步骤
1.初始标记:暂停用户线程,单线程进行标记GCRoots能够直接关联到的对象,快。
2.并发标记:执行GC Roots Tracing,和用户线程并发,标记所有的垃圾对象,慢。
3.重新标记:暂停用户线程,并且多线程修正 并发标记 时刚刚产生的对象的变化,快。
4.并发清除: 和用户线程并发,清除垃圾对象,慢。
CMS的默认年轻代队友是ParNew。
)
堆(年轻代+老年代)--------------------
G1(Garbage First)
1.将堆分成方块区域,每个区域的大小相等且都为2的次幂,大小范围是1M到32M,最多有2048个区域( Region)。
2.每个区域中都有一个Remembered Set来记录该区域内对象和其他区域的引用关系,在GC Roots Tracing阶段可以加快效率。
3.G1收集器维护了一个记录每个区域回收价值的列表,其中有可回收的内存空间大小和回收时间的信息,可以用来保证收集器的效率。
4.采用整体的标记整理算法,和局部的复制算法。
回收过程:
1.初始标记:暂停用户线程,单线程进行标记GCRoots能够直接关联到的对象,快。
2.并发标记:执行GC Roots Tracing,和用户线程并发,标记所有的垃圾对象,慢。
3.最终标记:暂停用户线程,并且多线程修正 并发标记 时刚刚产生的对象的变化,快。
4.筛选回收:接最终标记持续暂停用户线程,对各个区域的回收价值和时间进行排序,并且根据设定的GC时长来设定回收计划并回收,多线程执行,快。