GC为什么会导致应用程序卡顿?
GC线程导致工作线程停止,进而引发用户端出现卡顿现象
导致工作线程停止的机制叫做STW
首先理解GC线程与工作线程的关系
为了不产生垃圾回收时用户端的卡顿,最直观的方式是让GC线程与工作线程并行。而并行处理又会引发两个问题。
- 回收不彻底。GC开始时,不是垃圾(有引用),而GC结束时又变为垃圾(删除引用)
- 发生空指针异常。GC开始时是垃圾(还没被引用),当结束时又被使用(建立引用)
什么是STW
Stop the world。GC线程开启的同时暂停一切用户线程,造成卡顿
如何缓解
- 尽量减缓触发GC的次数
- 缩小GC范围
- 想办法让GC并行和并发
一些评价GC的指标
- 吞吐量:回收范围(堆)大小/GC总耗时
- 最大停顿耗时:最长STW时间
- 回收频率:回收的间隔时间频率,越低越好
- 响应时间:从一个实例成为垃圾,到他被释放的时间
- 空间利用率:堆中真正能够存储实例的空间占比
分代回收
- 老年代占堆的2/3,年轻代占1/3
- 新生区占年轻代的4/5
- 两个幸存区各占年轻代的1/10
每一个在内存中创建的实例,都有一个分代年龄。对象每经历一次GC,而没被回收,分代年龄会+1。
新创建的实例会存放在新生区中,如果新生区空间不足,触发针对整个年轻代的youngGC,触发后会清除掉新生区的垃圾实例,并将非垃圾实例移植其中一个幸存区,重复触发youngGC,会让幸存区两个空间内的实例来回移动,并将所有年龄+1。
如果幸存区中实例相同年龄的总和,大于幸存区的一半时,这些对象就会进入到老年代,或者年龄大于 -XX:MaxTenuringThreshold,从幸存区进入老年代,默认15,参数可配置。
如果老年代空间不足,触发youngGC时,将转而触发针对整个堆的fullGC,清理所有区域内的垃圾对象
分代回收本质上是一种基于经验,得出的垃圾回收优化策略,包括:
- 绝大多数的实例,生命周期都很短
- 熬过越多次垃圾回收过程的实例,越难以消亡
- 跨代引用,相对于同代引用来说仅占极少数
优点:
- 能够筛选出生命周期不同的实例,让他们在不同时机被回收,避免每次都针对整个堆进行回收
- 让不同的代,可以选择合适自己的不同的垃圾回收算法