几个名词
强软弱须
强引用
- 平时写的代码如Test obj = new Test();这种引用关系就是强引用
- 就算会OOM也不会回收
软引用 - 内存不足的情况下才会回收
- 如果发生了gc但是内存充足,依然不会回收
弱引用 - 只有发生gc就会回收
虚引用 - 形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
- 虚引用主要用来跟踪对象被垃圾回收的活动。
- 虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。
- 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
- 程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。
- 程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
GC类型
分代就是优化GC性能,把那些朝生夕死(新生代)的与链接对象(老不死的老年代)分开做垃圾收集
Minor GC(年轻代GC)
- Eden区满的时候会触发MinorGC,
- Survivor不会触发GC,Survivor随着Eden区MinorGC时一同被清理
- 大多数对象都是朝生夕死MinorGC出现的频率相对较高,STW时间相对较短
Major GC/Full GC (老年代GC)
- 出现Major GC,经常会伴随Minor GC,老年代空间不足的时候会先尝试触发MinorGC,如果还不够再触发MajorGC
- MajorGC比MinorGC慢10倍以上,会导致STW时间更长
- MajorGC后空间还不足就会OOM
Full GC
- 调用System.gc(),系统建议执行FullGC,但是不一定执行
- 老年代空间不足
- 方法区空间不足
- MinorGC后进入老年代的平均占用内存大小大于老年代可用空间
- 从Eden区+s0复制到s1区时,s1区大小存不下该对象,则把该对象转存到老年代,且老年代的可用内存也小于该对象
STW(Stop the world)
GC停顿
- 在执行GC时,jvm会停止其他用户线程的操作,防止产生新垃圾,会导致用户线程出现暂停,GC之后程序计数器和字节码执行引擎会继续从暂停处继续执行用户线程,对用户来说出现了短暂的卡顿,体验收到影响
为什么需要STW
- GC是从线程的gc root出发,对这个gc root链路可达性分析,
- 如果没有stw,一次GC从gc root出发的时候用户线程依然在运行,发现当前方法的gc root有应用,不是垃圾,但是稍后用户线程执行完成了,又变成了垃圾,导致这种情况的垃圾没法回收,这样会导致GC效果差,性能低下,所以出现STW,即GC停顿
TLAB(Thread Local Allocation Buffer))
- 堆内存共享,多线程情况下存在线程安全问题,加锁又会导致性能下降,所有未每个线程分配一个私有的缓存区域,包含在Eden空间内
- 多线程情况下,使用TLAB就可以避免线程安全问题,同时提升内存分配的吞吐量,因此被称为快速分配策略
- 使用-XX:UserTLAB 设置是否开启TLAB空间,默认开启TLAB且只占有Eden区的1%,但JVM将TLAB作为内存分配的首选,可以通过-XX:TLABWasteTargetPercent设置占比
- 如果TLAB分配失败,JVM就会尝试通过加锁机制确保数据的原子性,从而直接在Eden区分配内存
逃逸分析(Escape Analysis)
经过逃逸分析后发现,如果一个对象并没有逃逸出方法,就可能被优化为栈上分配,能使用局部变量的就不用定义为成员变量,-XX : +DoEscapeAnalysis 开启逃逸分析(默认开启)
- new 的对象在方法外被引用,逃逸
- return 出对象可能会在方法外使用,逃逸
- 成员变量赋值的可能发生方法外使用,逃逸
- 引用成员变量的值,逃逸
逃逸优化
- 栈上分配 : 将对分配转换为栈分配内存,逃逸分析没有逃逸的,方法结束,栈弹出就结束了,不需要对象回收,没有GC
- 同步省略 : 一个对象只能从一个线程被访问到,那么对这个对象的操作可以不再考虑同步
- 分离对象或标量替换 : 经过逃逸分析,发现一个对象不会被外界访问,经过JIT优化,就会把这个对象拆解为若干个成员变量来替换,标量替换为栈上分配提供更好的支持
a. 标量(scalar)是指无法在分割更小的数据的数据,如java中的原始数据类型就是标量;
b. 聚合量(aggreagate)可分解的数据
垃圾收集器
标记阶段标记的是存活对象,回收未被标记的对象。