垃圾回收(GC)
三个关注点:
1. 什么内存需要回收
根据内存区域来说,线程相关的(VM Stack, Native Method Stack,程序计数器)在线程结束时被回收,所以堆中的对象是GC的重点
判断对象是否可被回收,主流JVM使用根搜索算法,即判断对象是否有到任何一个GC Root对象的引用链,如果没有就是可以被回收的
GC Root对象包括:
VM Stack(本地变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(一般说的Native方法)引用的对象
引用的类型:
强引用,new出来的对象引用,通过GC回收
软引用(SoftReference),在内存溢出之前,会将弱引用对象二次回收,如果回收之后还不够分配,才溢出
弱引用(WeakReference),下次垃圾回收时,无论如何都会回收,生命周期就是下次回收之前
虚引用(PhantomReference),无法通过虚引用取得一个对象实例,唯一的作用就是在回收时可以得到通知
2. 什么时候回收
对于被根搜索到的对象,是否直接回收呢?
不是,JVM只是标记搜索到的对象,并判断对象是否实现了finzlize()方法并且是否执行过,如果不存在或执行过(那么对象可回收),如果未执行过实现的finalize(),那么将会把对象放入一个F-Queue队列,交给一个低优先级的finalize线程处理(如果这时将对象关联到一个GC Root上,对象将复活),同时会对F-Queue中的对象进行二次标记,然后回收
注意,finalize()方法只会执行一次,第二次搜索到的话,不会放入F-Queue队列中
方法区回收:会回收无用的类(回收效率较低)
无用的类需要满足:
类所有实例已经被回收
加载类的ClassLoader已经被回收
类的Class对象没有任何引用
3. 如何回收
回收算法:
1. 标记-清除回收, 即通过根搜索标记,然后直接清除,缺点会造成大量的内存碎片,导致之后的大对象无法分配
2. 标记-复制,先将活对象复制到一片内存中,然后之前这片内存全部清掉,缺点复制性能,并且JVM的内存只有一半可以使用
3. 标记-整理,先清除,然后将活的对象整理一下(移动位置)
4. 分代, 将堆分为年老代,年轻代(Eden,Survivor(2个)),当前使用
JVM的垃圾收集器类型:
7种,包括年轻代收集器: Serial, ParNew, Parallel Scavenge
年老代收集器:CMS, Serial Old, Parallel Old
还有新一代:G1
Serial, 单线程, 可搭配Serial Old 和 CMS使用,采用复制算法,优点简单高效(没有线程消耗),缺点(Stop the world),仍旧是client模式下的默认收集器
ParNew, Serial的多线程版本,可控参数(-XX:SurvivorRatio,-XX:PretenureSizeThreshold,-XX:HandlePromotionFailure等),可搭配Serial Old 和 CMS使用,是使用-XX:UseConcMarkSweepGC(使用CMS收集器)之后的默认年轻代收集器,也可以使用-XX:UseParNewGC来控制,可以使用-XX:ParallelGCThreads来控制线程数
Parallel Scavenge, 复制算法,可搭配Serial Old和Parallel Old,设计目的为了达到更高的吞吐量(CPU运行代码时间/CPU总耗时),-XX:MaxGCPauseMillis可设置最大垃圾回收停顿时间,-XX:GCTimeRatio设置吞吐量大小
自适应调节策略是Parallel Scavenge和ParNew的重要区别,是指在打开参数-XX:UseAdaptiveSizePolicy之后,不需要手动设置-Xmn(年轻代大小),-XX:SurvivorRatio(Eden和SurvivorRatio比例),-XX:PretenureSizeThreshold(晋升年老代的年龄(存活次数))
Serial Old,Serial的年老代版本,单线程,client模式下默认,标记-整理算法
Parallel Old,多线程,标记-整理,注重吞吐量的场合,使用Parallel Scavenge和Parallel Old的组合
CMS, 标记-清除算法(会导致碎片),目标是停顿时间短,包含四个步骤:
初始标记,并发标记,重新标记,并发清除,其中初始标记和重新标记过程也是停顿的
并发的回收,默认启动的线程数是CPU数量+3/4,默认情况下CMS在年老代占用68%之后启动垃圾回收,可以通过-XX:CMSInitiatingOccupancyFraction来配置,如果预留的内存不够并发分配,那么会导致出现Concurrent Mode Failure,然后JVM会启动Serial Old来收集
对于内存碎片,可以通过-XX:+UserCMSCompactAtFullCollection 开关,来设置每次FULL GC之后,启动一次内存整理(不可并发)
内存分配策略(默认Serial/Serial Old收集器的情况下,不同的收集器,可能不一样):
大多数的新对象,都会分配到Eden区域或者一个Survivor(From)中(经常触发Minor GC),如果GC之后,Eden中存活的对象会移入一个Survivor(To)中,可以配置参数-XX:PrintGCDetails来打印GC日志,可以配置-XX:PretenureSizeThreshold来配置对象直接分配Old区域,可以配置-XX:MaxTenuringThreshold来设置对象从年轻代提交到Old的年龄(存活次数)
还有一种情况,不到MaxTenuringThreshold的值的时候,移入Old,就是:
当Survivor中的相同年龄的对象达到Survivor空间的一半时,年龄大于等于这些对象的,都会被移入Old
担保失败, 对于Minor GC之前,JVM会判断Old区域的剩余大小,是否大于之前平均晋升的大小,是直接Minor GC,如果不是则会判断是否担保失败,如果是,也Minor GC,如果不是,则会进行Full GC(为了避免频繁Full,会开启此选项-XX:HandlePromotionFailure)