垃圾回收
这里直接贴上 github上一个比较全的java核心知识库项目 中的总结。
项目地址: Java Core Sprout
文章地址: 垃圾回收
HotSpot的算法实现
在GC时,HotSpot的算法实现,保证GC的安全。
- 可达性分析:使用一组OopMap的数据结构来存放对象的引用。
- 安全点:是否具有让程序长时间执行的特征,例如 方法调用,循环跳转,异常跳转等。
- 抢先式中断(没有虚拟机使用这种方式)
- 主动式中断
- 安全域:扩展了的安全点。
垃圾收集器
首先要了解
JVM两种运行模式Server与Client。相关博文可以参考这个JVM的Client模式与Server模式
-
垃圾收集器收集时的并行(Parallel)和并发(Concurrent)。
- 并行:指多条垃圾手机线程并行工作,但此时用户线程仍处于等待状态。
- 并发:指用户线程与垃圾收集线程同时执行(不一定是并行,可能会交替执行)。
-
Minor GC和Major GC/Full GC的不同
- 新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也非常快。
- Major GC/Full GC定义是相对明确的,就是针对整个新生代、老生代、元空(metaspace,java8以上版本取代perm gen)的全局范围的GC
分类
-
图中展示了7种不同分代的收集器:
Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1
-
而它们所处区域,则表明其是属于新生代收集器还是老年代收集器:
- 新生代收集器:
-
Serial:-XX:+UseSerialGC
复制算法
单线程收集
Stop The World
-
JVM Client模式
-
ParNew:-XX:+UseParNewGC
Serial收集器的多线程版本
JVM Server模式
默认开启的收集线程与CPU的数量相同,调节参数:-XX:ParallelGCThreads
-
指定使用CMS后,会默认使用ParNew作为新生代收集器,调节参数:-XX:+UseConcMarkSweepGC
-
- 新生代收集器:
-
Parallel Scavenge
- 复制算法
- 关注吞吐量:吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
- 多线程
- 应用场景:后台进行计算,而不需要与用户进行太多交互
- 参数调节:
- "-XX:MaxGCPauseMillis :控制最大垃圾收集停顿时间,大于0的毫秒数
- -XX:GCTimeRatio:设置垃圾收集时间占总时间的比率,0<n<100的整数
- -XX:+UseAdptiveSizePolicy: GC自适应的调节策略
-
老年代收集器
-
Serial Old
- 标记-整理算法
- 单线程
- JVM Client模式
-
Parallel Old:-XX:+UseParallelOldGC
- 标记-整理算法
- 多线程
- 使用场景:注重吞吐量以及CPU资源的敏感场合 Parallel Scavenge/Parallel Old
-
CMS:-XX:+UseConcMarkSweepGC
- 标记-清除算法
- 并发收集
- 使用场景:B/S系统或互联网站
- 对CPU资源敏感: CMS的默认收集线程数量是=(CPU数量+3)/4,
- 浮动垃圾
- 无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败,启动Parallel Old
- 产生大量内存碎片:标记-清除算法导致
- 参数:
- -XX:+UseCMSCompactAtFullCollection:FULL GC时开启内存碎片合并整理。
- -XX:+CMSFullGCsBeforeCompaction:多少次不压缩FULL GC后,进行压缩,默认值为0.
- -XX:CMSInitiatingOccupancyFraction: 调整出发GC百分比。
-
-
新生代/老年代收集器
- G1:-XX:+UseG1GC
并发与运行
分代收集
空间整合
可预测停顿
将整个堆划分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离,它们都是一部分Region(不需要连续)的集合, 每次根据允许的收集时间,优先回收价值最大的Region
FULL GC 不适用于该收集器 ,该收集器有自己的MixedGC。
-
参数:
- -XX:InitiatingHeapOccupancyPercent:当整个Java堆的占用率达到参数值时,开始并发标记阶段--global concurrent marking (类似于CMS收集器的收集算法);默认为45%;
- -XX:G1HeapWastePercent:在global concurrent marking结束之后,可以知道多少空间要被回收,在每次YGC之后和再次发生MixedGC 之前,会检查垃圾占比是否达到此参数,只有达到了,才会发MixedGC;
- -XX:MaxGCPauseMillis:为G1设置暂停时间目标,默认值为200毫秒
- -XX:G1HeapRegionSize:设置每个Region大小,范围1MB到32MB;目标是在最小Java堆时可以拥有约2048个Region
针对虚拟机 每个Region 对应一个Remembered Set,在GC根节点枚举范围加入Remembered Set,避免全堆扫描。
- G1:-XX:+UseG1GC
两个收集器间有连线,表明它们可以搭配使用:
其中Serial Old作为CMS出现"Concurrent Mode Failure"失败的后备预案。
内存分配
- 优先在Eden分配:Eden:Suvivor:Suvivor = 8: 1:1
- 大对象直接进入老年代
- 长期存活的对象将进入老年代:对象年龄计数器,默认为15.调节参数:-XX:MaxTenuringThreshold
- 动态对象年龄判定:虚拟机不是永远要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄的所有对象大小的总和大于Survior空间的一半,年龄大于或等于该对象就可以直接进入老年代
- 空间分配担保:在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以保证是安全的。如果不成立,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败,如果允许,那么就继续检查,看看老年代最大的连续可用空间是不是比历次新生代往老年代传的平均值大,如果大于,将会尝试一次Mionr GC,但是这是有风险的,如果小于,或者HandlePromotionFailure设置不允许冒险,就会改为进行一次Full GC。
GC日志
引用了网上一段解释,非常全了。
调整参数:-XX:+PrintGCDetails后输出的GC日志如下:
2014-07-18T16:02:17.606+0800: 611.633:
[GC 611.633: [DefNew: 843458K->2K(948864K), 0.0059180 secs]
2186589K->1343132K(3057292K), 0.0059490 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
解释如下:
2014-07-18T16:02:17.606+0800(当前时间戳): 611.633(时间戳):
[GC(表示Young GC) 611.633: [DefNew(单线程Serial年轻代GC):
843458K(年轻代垃圾回收前的大小)->2K(年轻代回收后的大小)
(948864K(年轻代总大小)), 0.0059180 secs(本次回收的时间)]
2186589K(整个堆回收前的大小)->1343132K(整个堆回收后的大小)
(3057292K(堆总大小)), 0.0059490 secs(回收时间)] [Times:
user=0.00(用户耗时) sys=0.00(系统耗时), real=0.00 secs(实际耗时)]
需要注意的是 GC 中括号的 英文缩写,代表了JVM使用的GC收集器。
如果是FULL GC 则说明这次GC 发生了 Stop-The-World。