判断对象是否存活
引用计数
假如对象被引用,则计数加1,计数为0时回收。但是存在循环引用问题:A引用B,B引用A,导致AB永远不能被回收。
可达性分析(JAVA采用的算法)
从GC Root出发看某个对象是否可达。假如不可达,则回收。
- GC Root:存活时间很长的对象,有存在意义的对象。
- 虚拟机栈中的局部变量表
- static成员
- 常量引用
- 本地方法栈中的变量
- 类加载器
- Thread
回收算法
标记-清除
流程:先标记对象,然后将未存活的对象清除。
缺点:1、大量的空间碎片,内存不连续;2、标记和清除都比较耗时。
标记-复制
流程:先标记存活对象,复制到保留区域,再将当前区域的所有对象清除。
缺点:浪费了一半空间
优点:空间连续
标记-整理
流程:先标记存活对象,将存活对象移动到一边,清理边界外的所有对象。
垃圾收集器
分代使用垃圾收集器
新生代:标记-复制算法
新生代大部分对象都是朝生夕死,只有少量对象存活,复制成本低,适合复制算法
- Serial:单线程,复制算法。单线程收集效率高;会停业务线程。
- ParNew:多线程,复制算法。多线程收集;会停业务线程。
- Parallel Scavenge:多线程,复制算法。更关注吞吐量。
老年代:标记-清除/标记-整理
老年代大量对象会存活,复制成本高,不适合使用复制算法
- Serial Old:单线程,标记-整理算法。会停业务线程。
- Parallel Old:多线程,标记-整理算法。更关注吞吐量。会停业务线程。
- CMS:Concurrent Mark Sweep(用户线程和垃圾回收线程可以同时进行),更关注停顿时间(降低了对吞吐量的要求)。标记-清除算法
G1收集器
G1作用于新生代和老年代,根据期望的停顿时间进行选择性回收。而且G1对堆重新进行了布局,逻辑上还存在Eden,Survivor,Old,但是物理上已经不隔离了。
JVM调优
- GC日志:停顿时间和吞吐量
- 吞吐量:运行用户代码时间/(运行用户代码时间+垃圾收集时间):用户代码执行占CPU资源时间比较大,跑任务,运算的任务(Parallel Scanvenge + Parallel Old)并发类收集器,jdk1.8默认
- 停顿时间:垃圾收集器进行垃圾回收Client执行响应的时间--->用户很好的体验--->和用户交互较多的场景(CMS,G1)并行类收集器,jdk1.9默认
- 内存使用的维度