内存空间是有限的,运行时如果不能获取到内存,会抛出 OutOfMemory
,一种有效的解决措施是,抛弃那些程序永远不会不再用到的对象,腾出空间。
给对象添加一个引用计数器,每当这个对象被引用一次就加1,每当这个对象的引用失效1次,就减1,那么引用次数为0的就没有再用了,非0就代表还有用,但是引用计数器很难解决循环引用。
新建对象的引用计数为1,如果两个新建对象互相引用,那么他们的引用计数为2,此时如果只将原新对象置为null,只会各自使得引用计数减1,这种场景下得到的结果引用结果是1,因而仅靠这种粗略的检查并不能达到一个好的效果
给对象的引用做追踪。可以定义一组集合,认定从这个集合出发,能够追溯到的所有对象,都是可用的,其余的都是不可用的
这种集合也称作GC Roots,它定义一组根引用,包括当前所有正在被调用的方法的引用类型参数、局部变量、临时值;方法区中的常量引用对象;本地方法栈中的JNI等等
干掉没有引用的对象,没什么问题,但是如果内存空间仍然不够,可以干掉部分虽然可用,但是不那么重要的对象来“确保大局”,java对此细分了强引用、软引用、弱引用、虚引用
详见 reference 引用
GC为什么要分代
要做回收,首先得知道哪些对象是可达的(存活的),而要知道可达性,对于对象引用追踪这种思想,就得要去遍历整个GC根集合。而要做到精准的枚举,就需要知道哪些栈的槽位有引用,哪些寄存器有引用,因而需要有一些位置去保存这些信息,而能够保存这些信息的地方即安全点或者安全区域。
能够保存这些信息的地方必定也是知道引用情况的地方,这些地方也就可以执行GC
无论使用哪种收集器,在收集开始的时候都是从 safepoint开始
"古老"的收集器,使用单线程收集,它工作时必须暂停所有用户的线程,直到收集结束。对于年轻代的收集则使用复制算法。 可以用于Client模式下的虚拟机。
serial的多线程版本。多线程收集,它工作时必须暂停所有用户的线程,直到收集结束。对于年轻代的收集则使用复制算法。 与CMS收集器配合工作,使用 -XX:UseConcMarkSweepGC
的默认年轻代收集器
多线程收集器。它的目标是提供一个可控的吞吐量:
与缩短停顿时间的收集器相比,它的目标是高效率的利用CPU的时间,尽快完成运算任务。另外它还支持自适应调节:比如年轻代大小、Eden和Survior的比例、晋升老年代的大小等,来达到最佳的吞吐量。适合后台运算而不需要太多交互的任务,不能配合CMS工作
缩短停顿时间的关注点则是在于提供良好的响应速度,从而提升用户体验
单线程收集。它需要暂停所有用户线程,直到收集结束。年老代使用标记-整理算法。它同样适用于Client模式下的虚拟机
如果是Server模式,在JDK1.5以及之前可以用来配合Parallel Scavenge搭配使用,以及作为CMS收集器的预备方案,在发生Concurrent Mode Failure时使用
多线程收集。它需要暂定所有用户线程,直到收集结束。使用标记整理算法,它也是以吞吐量优先,在JDK1.6中提供,用来配合Parallel Scavenge使用
并发收集。它分为4个阶段:
缺点:
Concurrent Mode Failure
,转而使用 Serial Old
,另外用户运行也会不断的产生垃圾,这部分无法清除(浮动垃圾), 因此有设置CMS触发的参数 -XX:CMSInitiatingOccupancyFraction
可以通过 -XX:CMSCompactAtFullCollection设置是否要清理碎片,以及 -XX:CMSFullGCsBeforeCompaction来表示多此次运行不压缩的Full GC后来一次压缩
新生代和老年代都可以收集。大致步骤如下:
与CMS相比优点:
JDK 7 引入
并发垃圾收集器。几乎所有的阶段都是并发执行
ZGC仍然会压缩堆,压缩堆这件事,通常意味着
压缩主要会遇到这么些问题
ZGC过程地址
JDK 11引入
名字讲解地址
Partial GC:不收集整个GC堆
Full GC:收集整个堆,包括年轻代,年老代,永久带(如果有的话)
Minor GC一般指的是young GC;Major GC通常和Full GC等价,另外由于名词混用,也可能指的是Old GC
触发young gc的时候,如果发现之前young GC的平均大小比目前老年代的剩余空间大,则触发Full GC,永久带如果没有足够的空间,也会触发Full GC
注意: ParallelScavenge 则是在每次触发Full GC之前会先执行一次young gc,再执行full gc;
使用jstat -gc pid time_interval count格式能够查看Java堆状况
结果如下
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 16960.0 16960.0 5116.0 0.0 136064.0 93854.9 339724.0 271888.9 152936.0 149578.7 20444.0 19683.3 220 2.122 19 0.923 3.045 复制代码
使用-XX:+PrintGCDetails可以显示GC的情况,形如
[GC[ParNew: 6996K->1202K(78656K), 0.0036460 secs][CMS: 0K->1163K(174784K), 0.0311840 secs] 6996K->1163K(253440K), [CMS Perm : 3060K->3059K(21248K)], 0.0349020 secs] [Times: user=0.03 sys=0.02, real=0.03 secs] 复制代码
方括号外
)的表示"GC前java堆已使用的容量->GC后Java堆使用的容量(Java堆总容量)" 墙钟时间包含各种非运算的等待耗时,例如等待磁盘IO,CPU时间则不包含这些,但是多线程会叠加CPU的时间