文章目录
1. JVM调优——垃圾回收器
1.1 分类
- 按照线程数分
- 串行
- 并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态(挂起); 如ParNew、Parallel Scavenge、Parallel Old;
- 并发(Concurrent):指用户线程与垃圾收集线程在一段时间内交替执行,gc线程执行时,用户线程会暂停(stop-the-world)
- 按工作的内存空间分
- 新生代垃圾回收器
- 老年代垃圾回收器
1.2 GC性能指标
- 吞吐量:程序运行时间占总运行时间(程序运行时间+gc时间)的比例
- GC负荷:gc时间占总运行时间(程序运行时间+gc时间)的比例
- 暂停时间:gc时,工作线程被暂停的时间——越小越好
- GC频率:
- 内存占用:Java堆区所占内存大小
- 快速:一个对象从诞生到被垃圾回收的时间
GC调优思路:
- 吞吐量大优先:降低gc频率,延长单次暂停时间来进行gc
- 暂停时间短优先:缩短单次暂停时间来进行gc,提高gc频率,吞吐量下降
1.3 详解
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、Parallel Old、CMS
- 整堆收集器:G1
-XX:+PrintCommandLineFlags//查看使用的默认垃圾回收器
垃圾回收器组合关系:
- jdk8、9:移除红色线
- jdk10:移除CMS
- jdk14:移除绿色线
1.3.1 串行垃圾回收器
1.3.1.1 Serial 垃圾回收器
- 使用复制算法、串行回收、stop-the-world机制
- 应用场景:
- HotSpot在Client模式下默认的新生代收集器,单线程收集效率高,避免线程切换导致的额外开销
- 在用户的桌面应用场景中,可用内存一般不大(几十M至一两百M),可以在较短时间内完成垃圾收集(几十MS至一百多MS),只要不频繁发生,这是可以接受的
1.3.1.2 Serial Old 垃圾回收器
- 使用标记-压缩算法、串行回收、stop-the-world机制
- 应用场景:
- HotSpot在Client模式下默认的老年代收集器,单线程收集效率高
- HotSpot在Server模式下:
- 与新生代Parallel Scavenge配合使用
- 作为老年代CMS的后备收集方案
-XX:+UseSerialGC //使用Serial+Serial Old
- Serial+Serial Old
1.3.2 并行垃圾回收器
1.3.2.1 ParNew垃圾回收器
- 使用复制算法、并行回收、stop-the-world机制
- 应用场景:
- 在Server模式下,ParNew收集器是一个非常重要的收集器。新生代,回收次数频繁,并行回收效率高
-XX:+UseConcMarkSweepGC //指定使用CMS后,会默认使用ParNew作为新生代收集器;
-XX:+UseParNewGC //强制指定使用ParNew;
-XX:ParallelGCThreads //指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
- ParNew + Serial Old
1.3.2.2 Parallel Scavenge:吞吐量优先
- 使用复制算法、并行回收、stop-the-world机制
- 自适应调节策略
- 应用场景:
- 后台运行不需要与用户交互:批量处理、计算、支付等
- jdk8,server端默认的年轻代垃圾回收器
1.3.2.3 Parallel Old:吞吐量优先
- 使用标记-压缩算法、并行回收、stop-the-world机制
- 自适应调节策略
- 应用场景:
- 后台运行不需要与用户交互:批量处理、计算、支付等
- jdk8,server端默认的老年代垃圾回收器
-XX:+UseParallelGC //使用Parallel Scavenge + Parallel Old
-XX:ParallelGCThreads //设置年轻代并行收集的线程数,一般等于cpu数
-XX:MaxGCPauseMillis //用户线程最大暂停时间
-XX:GCTimeRatio //设置gc时间占总运行时间的比例
-XX:+UseAdptiveSizePolicy //开启自适应调节
- Parallel Scavenge+Parallel Old
1.3.3 并发垃圾回收器
1.3.3.1 CMS:低延迟
-
使用标记-清除算法、并发回收、stop-the-world机制
-
应用场景:
- 与用户直接交互的场景
-
过程
- 初始标记:仅标记一下GC Roots能直接关联到的对象,虽然stop-the-world,但执行非常快,耗时短
- 并发标记:从GC Roots直接关联的对象开始遍历整个对象图的过程,耗时长,但不stop-the-world
- 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记变动的那一部分对象的标记记录,虽然stop-the-world,但并行执行,耗时短
- 并发清除:清除被标记的所有的垃圾对象,释放内存空间
- CMS不像别的垃圾回收器在空间快满了才垃圾回收,而是达到一个阈值就开始垃圾回收,如果CMS运行期间报错,会临时使用Serial Old
- 缺点:
- 标记-清除会产生内存碎片,在为新对象分配内存空间时,不能使用指针碰撞技术,而是使用空闲列表执行内存分配
- 为什么不使用标记-压缩?因为清理时用户线程还在并发执行
- 产生的碎片导致无法分配大的对象,提前触发full gc
- 无法处理浮动垃圾:并发清理阶段变成的垃圾无法被CMS标记,这些对象在该次gc时不会被回收,从而需要下一次gc
-XX:+UseConcMarkSreepGC //手动使用ParNew + CMS
-XX:CMSInitiatingOccupanyFraction //设置堆内存使用率的阈值
-XX:+UseCMSCompactAtFullCollection //执行完full gc后进行压缩整理
-XX:+CMSFullGCsBeforeCompaction //设置执行多少次不压缩的Full GC后,来一次压缩整理
-XX:ParallelCMSThreads //cms线程数
1.3.4 G1 (G First)区域化分代 并行+并发:吞吐量优先
- 出现原因:业务越来越复杂、经常stw接受不了,内存和处理器的提升有助于增大吞吐量、降低延迟时间
- 目标:延迟可控,高吞吐量
- 工作空间:整个堆的垃圾收集
- 应用场景:
- 面向服务端,针对多核cpu及大内存的机器
- jdk9 默认的垃圾回收器
- 优势:
- 并行性:有多个gc线程同时工作,会stop-the-world
- 并发性:部分gc线程可以与用户线程同时执行
- 分代收集:同时兼顾新生代、老年代,但不要求新生代、老年代在空间上连续,也不为新生代、老年代固定大小
- 空间整合:g1将空间划分成一个个region,region之间使用复制算法,整体上是标记-压缩,相比cms,有利于程序长时间运行,大对象时不会像cms提前full gc
- 可预测暂停时间模型:
- 缺点:相比cms会占用额外内存空间,小内存时cms表现更好
-XX:+UseG1GC //jdk9之前手动指定
-XX:G1HeapRegionSize //指定region大小,根据最小的堆内存划分成2048个区域
-XX:MaxGCPauseMillis //期望达到的最大gc停顿时间,过小单次gc的对象少,过大停顿时间长
-XX:InitiatingHeapOccupancyPercent //当整个Java堆的占用率达到参数值时,开始并发标记阶段
-XX:ParallelGCThread //并行时gc线程数,会stop-the-world
-XX:ConcGCThreads //并发时gc线程数
-
使用方法:
- 开启g1
- 设置最大堆内存
- 设置最大停顿时间
-
应用场景:
- server端,大内存,多处理器,低延迟,大堆
- 部分场景代替cms:
- 堆空间一半以上区域被活动数据占用
- 对象分配、晋升频率变化很大
- gc停顿时间长,大于0.5-1s
-
每一个region只能是eden、survivor或者old区,humongous区用于存放大对象(超过1.5个region大小,为了防止短期存在的大对象造成内存泄露问题,如果一个h区放不下,会使用多个连续的h区,找不到多个连续的h区,会full gc)
-
Remember Set
- 如果region对象被其他region引用,是否需要扫描整个堆来判断是否对象是否存活?
- 不需要,每个region都分配一个Remember Set,记录别的region的对象对当前region的对象的引用,最后在gc roots中加入Remember Set
- 过程1:年轻代gc
- 过程2:老年代并发标记
- 过程3:混合回收(年轻代+一部分老年代)
- 过程4:full gc(尽可能避免)