如何判断对象可以回收
引用计数法
存活的对象被使用一次就计数1,当计数为0时就回收。
存在问题:循环引用(A和B对象互相引用,导致无法触发垃圾回收)。
可达性分析算法
根对象
系统类加载器加载的class对象,静态变量的引用。
方法中局部变量和参数。
所有活动的Java线程。
可能作为根对象
用作同步的监视器对象。
由 JVM 实现定义的特定对象,这些对象没有为其目的进行垃圾收集。它可能包含重要的异常类、系统类加载器或自定义类加载器
根对象不能被回收,在每次回收之前,对堆中存活的所有对象进行扫描,判断是否跟根对象有直接或者间接的引用,如果有,则不回收。
四种引用
- 强引用
只要有引用关系,就不回收。 - 软引用
在发生内存溢出异常之前,把这些对象加入回收范围,进行第二次回收,如果还不够,则抛异常。 - 弱引用
只要有垃圾回收,就会被回收。 - 虚引用
唯一的作用是被垃圾回收后收到系统通知。
垃圾回收算法
标记清除
- 标记具有根节点引用的对象
- 清除未标记的对象
- 优点:速度快
- 缺点:产生内存碎片
标记整理
- 标记具有根节点引用的对象
- 清除未标记对象
- 将标记对象移动到一起
- 优点:不产生内存碎片
- 缺点:速度慢
复制
- 标记具有根节点引用的对象
- 复制具有标记的对象到TO区
- 清除FROM区
- 交换FROM与TO区
- 优点:不产生内存碎片
- 缺点 需要双倍内存空间
分代垃圾回收
- 对象首先分配在Eden区,Eden不够时,触发Minor GC,将存活对象复制到To区,存活对象年龄加一,交换From和To区的元素,From区的元素经过一定次数的GC(默认16次,年龄最大15,跟对象头有关)则复制到老年代。
- 当老年代空间不足,会触发minor gc,仍然空间不足,则会触发full gc。
- minor gc 会触发 stop the word
垃圾回收器
串行
- 单线程
- 堆内存较小,适合单核cpu
- 新生代:复制
- 老年代:标记整理
吞吐量优先
- 多线程
- 堆内存较大,STW的时间占整体时间比重较小。
响应时间优先
- 多线程
- 堆内存较大,尽可能让单次STW的时间较小。
G1
同时注重吞吐量和低延时,默认的暂停目标是200ms。
超大堆内存,将堆划分为多个大小相等的Region
整体上是标记整理算法,两个区域之间是复制算法
垃圾回收阶段
-
新生代收集
-
新生代收集+并发标记
-
混合收集
新生代
- 所有new操作的对象分配非常廉价
- TLAB thread-local allocation buffer
- 死亡对象的回收代价是零
- 大部分对象用过即死
- Minor GC的时间远远低于 Full GC
调优
- -Xmn 堆中新生代的初始最大大小,大于25%,小于50%。
- 空间过小导致频繁的Minor GC,空间过大导致长时间的Full GC。
幸存区
- 复制时耗时。
- 需要足够大的空间以容纳活跃的对象以及晋升的对象。
老年代
以CMS为例
- CMS的老年代内存越大越好
- 观察是否是由老年代内存导致的问题,调优从新生代开始