对象被判断为垃圾的标准
引用计数法
目前主流的垃圾收集器实现都没有采用这种算法
- 每个对象实例都有一个引用计数器,被引用则+1,取消引用则-1。
- 通过判断对应的引用数量来决定是否可以被回收
- 任何引用计数为0的对象实例都可以被当作垃圾回收
优点: 执行效率高,程序执行受影响较小
缺点: 无法检测出循环引用的情况,导致内存泄漏
可达性分析
通过判断GC Root对象的引用链是否可达来决定对象是否可以被回收
可以作为GC Root的对象
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法区中常量引用的对象
- 方法区中类的静态属性引用的对象
- 本地方法栈中JNI(native方法)的引用对象
垃圾回收算法
标记-清除算法(Mark and Sweep)
标记待回收对象,然后清除,会有内存碎片化问题
复制算法
解决碎片问题,顺序分配内存,简单高效,适用于对象存活率低的场景(新生代)
缺点:浪费内存一小段的内存
标记-整理算法
标记: 对存活的对象标记
整理: 移动所有存活的对象,且按照内存地址依次排序,然后将末端内存地址以后的内存全部回收。
避免内存的不连续性,不用设置两块内存互换,使用于存活率高的场景。(老年代)
分代收集算法
新生代使用复制算法,老年代使用标记-整理算法
Minor GC 是新生代垃圾收集
Full GC 是老年代垃圾收集,一般会伴随Minor GC
年轻代:尽可能快速的收集生命周期短的对象
对象如何晋升到老年代
- 默认经历15次Minor GC依然存活的对象
- Survivor区中存在放不下的对象
- 大对象直接进入老年代(-XX:+PretenuerSizeThreshold),默认全部先放年轻代。
触发Full GC的条件
- 老年代空间不足
- CMS GC时出现promotion failed,concurrent mode failure
- Minor GC晋升到老年代的平均大小大于老年代的剩余空间
- 当MinorGC后剩余的对象占用空间,大于Survivor to空间的50%(根据配-XX:TargetSurvivorRatio=50), 也会将年龄最大的(即使没有对象年龄没达到15)那部分对象移入老年代;
例如: 年轻代空间为1G,其Survivor空间为100M.
年龄为3的对象: 10M
年龄为2的对象: 20M
年龄为1的对象: 10M
Eden区存活对象: 11M
此时进行MinorGC, 所有存活对象共51M,大于Survivor空间的一半,则年龄为3的10M对象会被移入老年代. - 当Survivor空间中相同年龄所有对象的大小总合大于Survivor空间的50%(根据配-XX:TargetSurvivorRatio=50), 则年龄大于等于这个年龄的对象也会直接进入老年代;
- 显示调用System.gc()
- 使用RMI来进行RPC或者管理的JDK应用,每小时执行1次Full GC
Stop-the-World
JVM由于要执行GC而停止了应用程序的执行,除了GC线程,其它应用程序线程全部等待状态。多数GC优化通过减少Stop-the-World发生的时间来提高程序性能。
Safepoint
可达性分析中过程中,对象引用关系不会发生变化的点
垃圾收集时,应用程序不是在随便哪个点都停顿下,而是到达安全点才会停顿。
产生Safepoint的地方:方法调用,循环跳转,异常跳转等位置
GC过程中如果有线程不在安全点,会等到所有的线程都执行到安全点再执行GC。
常用的调优参数
-XX:SurvivorRatio
Eden和Survivor的比值,默认8:1:1
-XX:NewRatio
老年代和年轻代内存大小的比例
-XX:MaxTenuringThreshold
对象从年轻代晋升到老年代经历的GC次数最大阈值
垃圾收集器
年轻代收集器
Serial收集器(-XX:+UseSerialGC,复制算法)
- 单线程收集,进行垃圾收集时,必须暂停所有工作线程
- 简单高效,JDK Client模式下默认的年轻代收集器
ParNew 收集器(-XX:+UseParNewGC,复制算法)
- 多线程收集,其余的行为特点和Serial收集器一样
- 在多核下执行才有优势,单核执行效率不如Serial
- 默认开启的线程数和CPU数量相同,可以通过参数控制线程数
- 在Server模式下是非常重要的收集器,除Serial外,目前唯一可以和CMS配置使用的收集器
Parallel Scavenge收集器(-XX:+UseParallelGC,复制算法)
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
更短暂的停顿时间,可以快速响应用户请求,给用户良好的体验。而高吞吐量可以高效率利用CPU时间,尽可能快的完成运算任务。适合在后台运算,不需要太多交互的情况。
- 比起关注用户线程停顿时间,该收集器更加关注系统的吞吐量。
- 在多核执行下才有优势,Server模式下默认的年轻代收集器
- 可以通过参数
-XX:+UseAdaptiveSizePolicy
由虚拟机自动调节内存设置
-XX:ParallelGCThreads=数字N
表示启动多少个GC线程
-XX:+UseParallelGC
= 新生代ParallelScavenge + 老年代ParallelOld
-XX:-UseParallelOldGC
= 新生代ParallelScavenge + 老年代SerialOld
老年代收集器
Serial Old收集(-XX:+UseSerialOldGC,标记-整理算法)
- 单线程收集,收集时需要暂停所有工作线程
- 简单高效,Client模式下默认的老年代收集器
Parallel Old收集器(-XX:+UseParallelOldGC,标记-整理算法)
- 多线程,吞吐量优先
CMS收集器(-XX:+UseConcMarkSweepGC,标记-清除算法)
-
几乎能与用户线程同时工作
-
尽可能缩短用户线程停顿时间
-
如果有较多相对存活较长的对象,更适合使用CMS
CMS垃圾回收过程
- 初始标记 需要stop-the-world,从垃圾回收的根对象开始,扫描能和根对象直接关联的对象并标记。
- 并发标记 并发追溯标记,应用程序不需要停顿
- 并发预清理 查找执行并发标记阶段从年轻代晋升到老年代的对象
- 重新标记 暂停用户线程,扫描CMS堆中的剩余对象
- 并发清理 清理垃圾对象,程序不会停顿
- 并发重置 重置CMS收集器的数据结构
缺点: 采用的标记-清除,会产生内存碎片。老年代空间会随着应用时长被逐步耗尽,最后将触发担保机制对内存进行压缩,(Serial Old
)串行老年代收集器将会以Stop the world方式进行一次GC,从而造成较大停顿时间。
CMS也提供了参数-XX:CMSFullGCsBeForeCompaction
来指定执行多少次full GC才会做压缩,默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩
G1收集器(-XX:+UseG1GC, 复制 + 标记-整理算法)
既用于新生代也用于老年代
- G1能够充分利用多CPU,多核环境硬件优势,尽量缩短STW时间
- G1整体上采用标记-整理算法,局部是通过复制算法(不会产生内存碎片)
- 宏观上看G1不再区分新生代和老年代,把内存划分成多个独立的子区域(Region),可以近似理解为一个围棋的棋盘.
- G1收集器里面将整个的内存区域都混合在一起,但其本身依然在小范围内进行年轻代和老年代的区分
- 整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的survivor堆做复制准备每个分区都可能随G1的运行在不同代中间切换
各收集器可搭配方式
有连线表示可以搭配使用
Object的finalize()方法的作用是否与C++的析构函数相同
- 与C++的析构函数不同,析构函数调用确定,而finalize()不确定
- 将未被引用的对象放置在F-Queue队列
- 方法执行随时可能被终止
- 给了对象最后依次重生的机会