前言
对象分配之后,那么在对象不再被需要的时候就需要被回收了
0x01 垃圾判断
引用计数法:每次引用,都对对象实例
的引用计数+1,如果取消引用则-1,为0则是垃圾。但是存在循环引用的bug
可达性分析:gc roots分析,就是如果还有变量引用那么就不是垃圾,没有变量引用则是垃圾。可达性分析会造成gc停顿(分析过程中引用不应该变化,所以停顿系统)
0x02 垃圾收集流程
第一步:判断是否垃圾并标记
通过gc roots判断该对象实例是否是垃圾,如果是则标记
第二步:筛选第一次标记
如果该对象实例重写finalize方法并且没有执行过finalize方法,则放入F-Queue队列执行(并不保证运行结束),如果在finalize中重新连上了gc roots则去除标记。该对象实例不会被回收
0x03 垃圾收集算法
通过垃圾收集算法决定了如何收集垃圾
复制算法
原理:将内存进行等分,每次将其中一半内存中存活的对象复制到另一半内存中。
缺点:造成内存的浪费
HotSpot实现:
- 堆内存继续划分为Eden空间和2个Survivor空间,每次使用Eden空间和其中一块Survivor空间
- 垃圾收集的时候将存活的复制到另一块Survivor空间,清除Eden和之前使用的Survivor空间
- 如果另一块Survivor空间不足以存放,则将其放入老年代
标记-清除算法
原理:每次标记内存是否垃圾,是则清除
缺点:内存不连续,可能下次内存分配会导致一次垃圾收集
标记-整理算法
原理:标记之后,先将所有存活对象移动到某一端,然后清除该端之外的内存
分代收集算法
根据对象存活周期的不同将内存划分为几块,一般是把Java堆进一步划分为新生代、老年代。根据不同年代的特性选择适合的收集算法
问题:为什么这么划分?
因为复制算法适合朝生夕灭的区域的垃圾回收(新生代),标记-清除和标记-整理适合长时间不死的对象回收(老年代)
0x04 垃圾收集器
Serial收集器
收集内存:新生代
特征:
- 历史最悠久
- 单线程收集
- 单CPU环境收集效率较高
- 适合Client模式
jvm参数:
- -XX:+UseSerialGC:使用Serial收集器
ParNew收集器
收集内存:新生代
特征:
- Serial的多线程版本
- 只有它能和CMS配合工作
jvm参数:
- -XX:+UseParNewGC:使用ParNew收集器
- -XX:ParallelGCThreads:限制垃圾收集的线程数
Parallel Scavenge收集器
收集内存:新生代
主要目的:不同于其他收集器,该收集器主要为了提高吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),提高响应速度
特征:
- 停顿时间短适合与用户交互的程序,高吞吐量可以高效利用CPU时间,适合后台运算不需要交互的程序
- 注重吞吐量
- 多线程
jvm参数:
- -XX:MaxGCPauseMillis:设置gc停顿时间,但是牺牲吞吐量和新生代空间(gc停顿时间短了,新生代空间会自动缩小,所以发生gc频率高了)
- -XX:GCTimeRatio:设置最大gc时间,相当于吞吐量的倒数,默认99
- -XX:+UseAdaptiveSizePolicy:不需要手工指定新生代大小、晋升老年代年龄等参数,会自动设置,只要设置好堆最大内存和上面2个参数即可
Serial Old收集器
收集内存:老年代
特征:
- 配合Serial收集器,主要用于Client模式
- Server模式下
- jdk1.5之前和Parallel Scavenge搭配
- 作为CMS的后备预案,当CMS出现并发收集错误,jvm自动切换到该收集器对老年代进行收集
Parallel Old收集器
收集内存:老年代
特征:
- 配合Parallel Scavenge收集器
jvm参数:
- -XX:+UseParallelOldGC:使用Parallel Old收集器
CMS收集器
收集内存:老年代
特征:
- 低gc停顿
流程:
- 初始标记(CMS initial mark)
- 停顿系统
- 标记gc roots对象,很快
- 并发标记(CMS concurrent mark)
- gc roots gracing过程
- 重新标记(CMS remark)
- 停顿系统
- 修正并发标记期间程序继续运行发生变动的对象
- 比初始标记慢,比并发标记快
- 并发清除(CMS concurrent sweep)
缺点:
- cpu资源敏感,很容易造成cpu资源占用
- 无法处理浮动垃圾
- 所以需要预留部分空间,在达到阈值的时候就开始FullGC
- cms运行期间内存不足,抛出Concurrent Mode Failure导致另一次Full GC(Serial Old收集器来回收)
- -XX:CMSInitiatingOccupancyFraction设置触发的值,设置过高容易出现CMF错误
- 可能由于空间碎片导致另一次Full GC
- 内存整理过程无法并发,停顿时间变长
- -XX:+UseCMSCompactAtFullCollection开关在顶不住要FullGC的时候开启内存碎片整理
- -XX:CMSFullGCsBeforeCompaction设置多少次不压缩的FullGC后来一次压缩(默认0表示每次FullGC后都整理)
G1收集器
0xPS1 JVM可用参数
新生代参数
参数 | 解释 |
---|---|
-XX:SurvivorRatio | 新生代中Eden区域与Survivor区域的容量比值,默认为8,代表Eden:Survivor = 8:1 |
-XX:PretenureSizeThreshold | 直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
-XX:+HandlePromotionFailure | 老年代内存不足以担保Eden和Survivor的机端情况 |
老年代参数
0xPS2 Java引用
在垃圾引用的时候根据引用类型不同做不同的处理
强引用
类似Object obj = new Object()
则是强引用,不会被垃圾回收
软引用
描述一些还有用但不是必须的对象,在内存即将发生溢出异常之前,会把这些对象放进回收范围内进行第二次回收,如果回收后内存仍然不足,抛出OOM异常
弱引用
比软引用还弱一些,只能生存到下一次垃圾回收
虚引用
无法通过虚引用获取一个对象实例,只是为了在该对象被回收时收到一个系统通知
0xPS3 内存分配策略
内存大多分配在堆上,也有分配在TLAB中(参考系列二),优先在Eden区,但是也可能直接进老年代。分配的规则随着一些前置条件而变化
对象优先在Eden分配
- 若Eden内存不足则发起Minor GC
大对象直接进老年代
根据jvm参数-XX:PretenureSizeThreshold
可以将超过该值的内存直接分配在老年代,如果老年代内存不足则触发FullGC
长期存活对象进入老年代
每次minor gc都将存活的对象年龄+1,当年龄超过-XX:MaxTenuringThreshold
该值则进入老年代,若老年代内存不足则触发FullGC
动态对象年龄判定
如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间一半,年龄大于或等于该年龄的对象就可以直接进入老年代
空间分配担保
minor gc之前,虚拟机先检查老年代最大可用的连续内存是否大于新生代所有对象总空间,如果条件成立,minor gc是安全的。
若条件不成立,则根据参数-XX:+HandlePromotionFailure
是否开启检查是否允许担保失败,如果允许则检查老年代最大可用的连续空间是否大于历次晋升老年代对象的平均大小,如果大于则尝试进行一次minor gc,小于或者不允许担保失败则进行full gc
0xFF 总结
将堆内存继续划分是为了更好更高效的回收内存,