(https://gceasy.io 专业日志分析网站下文有介绍)
目录
GC 算法 GC 收集器 GC 调优实战 GC 常用参数
GC 算法
标记 - 清除算法
它是最基础的收集算法, 这个算法分为两个阶段,"标记" 和 "清除". 首先标记出所有需要回收的对象, 在标记完成后统一回收所有被标记的对象. 它有两个不足的地方:
1. 效率问题, 标记和清除两个过程的效率都不高;
2. 空间问题, 标记清除后会产生大量不连续的碎片;
复制算法
为了解决效率问题, 复制算法出现了. 它可以把内存分为大小相同的两块, 每次只使用其中的一块. 当这一块的内存使用完后, 就将还存活的对象复制到另一块区, 然后再把使用的空间一次清理掉. 这样就使每次的内存回收都是对内存区间的一半进行回收.
标记 - 整理算法
根据老年代的特点提出的一种标记算法, 标记过程和 "标记 - 清除" 算法一样, 但是后续步骤不是直接对可回收对象进行回收, 而是让所有存活的对象向一段移动, 然后直接清理掉边界以外的内存.
分代收集算法
现在的商用虚拟机的垃圾收集器基本都采用 "分代收集" 算法, 这种算法就是根据对象存活周期的不同将内存分为几块. 一般将 java 堆分为新生代和老年代, 这样我们就可以根据各个年代的特点选择合适的垃圾收集算法.
在新生代中, 每次收集都有大量对象死去, 所以可以选择复制算法, 只要付出少量对象的复制成本就可以完成每次垃圾收集. 而老年代的对象存活几率时比较高的, 而且没有额外的空间对它进行分配担保, 就必须选择 "标记 - 清除" 或者 "标记 - 整理" 算法进行垃圾收集.
GC 收集器
新生代采用复制算法, 老年代采用标记 - 整理算法.
Serial 收集器
Serial(串行)收集器收集器是最基本, 历史最悠久的垃圾收集器了. 大家看名字就知道这个收集器是一个单线程收集器了. 它的 "单线程" 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作, 更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ), 直到它收集结束.
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验, 所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿, 寻找最优秀的垃圾收集器的过程仍然在继续). 但是 Serial 收集器有没有优于其他垃圾收集器的地方呢? 当然有, 它简单而高效(与其他收集器的单线程相比).Serial 收集器由于没有线程交互的开销, 自然可以获得很高的单线程收集效率. Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择.
ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本, 除了使用多线程进行垃圾收集外, 其余行为 (控制参数, 收集算法, 回收策略等等) 和 Serial 收集器完全一样.
它是许多运行在 Server 模式下的虚拟机的首要选择, 除了 Serial 收集器外, 只有它能与 CMS 收集器 (真正意义上的并发收集器, 后面会介绍到) 配合工作.
Parallel Scavenge 收集器
Parallel Scavenge 收集器类似于 ParNew 收集器.
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU).CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验). 所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值. Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量, 如果对于收集器运作不太了解的话, 手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择.
Serial Old 收集器
Serial 收集器的老年代版本, 它同样是一个单线程收集器. 它主要有两大用途: 一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用, 另一种用途是作为 CMS 收集器的后备方案.
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本. 使用多线程和 "标记 - 整理" 算法. 在注重吞吐量以及 CPU 资源的场合, 都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器.
CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器. 它而非常符合在注重用户体验的应用上使用. CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器, 它第一次实现了让垃圾收集线程与用户线程 (基本上) 同时工作. 从名字中的 Mark Sweep 这两个词可以看出, CMS 收集器是一种 "标记 - 清除" 算法实现的, 它的运作过程相比于前面几种垃圾收集器来说更加复杂一些. 整个过程分为四个步骤:
初始标记(CMS initial mark): 暂停所有的其他线程, 并记录下直接与 root 相连的对象, 速度很快. 并发标记(CMS concurrent mark): 同时开启 GC 和用户线程, 用一个闭包结构去记录可达对象. 但在这个阶. 段结束, 这个闭包结构并不能保证包含当前所有的可达对象. 因为用户线程可能会不断的更新引用域, 所以 GC. 线程无法保证可达性分析的实时性. 所以这个算法里会跟踪记录这些发生引用更新的地方. 重新标记(CMS remark): 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生. 变动的那一部分对象的标记记录, 这个阶段的停顿时间一般会比初始标记阶段的时间稍长, 远远比并发标记阶段时间短. 并发清除(CMS concurrent sweep): 开启用户线程, 同时 GC 线程开始对为标记的区域做清扫.
CMS 主要优点: 并发收集, 低停顿. 但是它有下面三个明显的缺点:
对 CPU 资源敏感; 无法处理浮动垃圾; 它使用的回收算法 -"标记 - 清除" 算法会导致收集结束时会有大量空间碎片产生. G1 收集器
G1 (Garbage-First)是一款面向服务器的垃圾收集器, 主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时, 还具备高吞吐量性能特征.
G1 收集器的运作大致分为以下几个步骤:
初始标记并发标记最终标记筛选回收 G1 收集器在后台维护了一个优先列表, 每次根据允许的收集时间, 优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来). 这种使用 Region 划分内存空间以及有优先级的区域回收方式, 保证了 GF 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零).
怎么选择垃圾收集器?
1. 优先调整堆的大小让服务器自己来选择
2. 如果内存小于 100m, 使用串行收集器
3. 如果是单核, 并且没有停顿时间的要求, 串行或 JVM 自己选择
4. 如果允许停顿时间超过 1 秒, 选择并行或者 JVM 自己选
5. 如果响应时间最重要, 并且不能超过 1 秒, 使用并发收集器
官方推荐 G1, 性能高
GC 调优实战
JVM 调优主要就是调整下面两个指标:
停顿时间: 垃圾收集器做垃圾回收中断应用执行的时间.-XX:MaxGCPauseMillis
吞吐量: 垃圾收集的时间和总时间的占比: 1/(1+n), 吞吐量为 1-1/(1+n).-XX:GCTimeRatio=n
调优步骤
1. 打印 GC 日志 - XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc.log
2. 分析日志得到关键性指标
3. 分析 GC 原因, 调优 JVM 参数
Parallel Scavenge 收集器(默认)
java -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:./gc-parallel-1.log -jar logback-0.0.1-SNAPSHOT.jar
调优前: gc-parallel-1.log, 通过 https://gceasy.io 打开, 会看到如下界面:
调优后: gc-parallel-2.log, 通过 https://gceasy.io 打开, 会看到如下界面:
调优前后结果对比
以上测试结果是在 Jdk1.8 环境下的默认 GC 收集器进行测试的结果.
GC 常用参数
堆栈设置
-Xss: 每个线程的栈大小
-Xms: 初始堆大小, 默认物理内存的 1/64
-Xmx: 最大堆大小, 默认物理内存的 1/4
-Xmn: 新生代大小
-XX:NewSize: 设置新生代初始大小
-XX:NewRatio: 默认 2 表示新生代占年老代的 1/2, 占整个堆内存的 1/3.
-XX:SurvivorRatio: 默认 8 表示一个 survivor 区占用 1/8 的 Eden 内存, 即 1/10 的新生代内存.
-XX:MetaspaceSize: 设置元空间大小
-XX:MaxMetaspaceSize: 设置元空间最大允许大小, 默认不受限制, JVM Metaspace 会进行动态扩展.
垃圾回收统计信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
收集器设置
-XX:+UseSerialGC: 设置串行收集器
-XX:+UseParallelGC: 设置并行收集器
-XX:+UseParallelOldGC: 老年代使用并行回收收集器
-XX:+UseParNewGC: 在新生代使用并行收集器
-XX:+UseParalledlOldGC: 设置并行老年代收集器
-XX:+UseConcMarkSweepGC: 设置 CMS 并发收集器
-XX:+UseG1GC: 设置 G1 收集器
-XX:ParallelGCThreads: 设置用于垃圾回收的线程数
并行收集器设置
-XX:ParallelGCThreads: 设置并行收集器收集时使用的 CPU 数. 并行收集线程数.
-XX:MaxGCPauseMillis: 设置并行收集最大暂停时间
-XX:GCTimeRatio: 设置垃圾回收时间占程序运行时间的百分比. 公式为 1/(1+n)
CMS 收集器设置
-XX:+UseConcMarkSweepGC: 设置 CMS 并发收集器
-XX:+CMSIncrementalMode: 设置为增量模式. 适用于单 CPU 情况.
-XX:ParallelGCThreads: 设置并发收集器新生代收集方式为并行收集时, 使用的 CPU 数. 并行收集线程数.
-XX:CMSFullGCsBeforeCompaction: 设定进行多少次 CMS 垃圾回收后, 进行一次内存压缩
-XX:+CMSClassUnloadingEnabled: 允许对类元数据进行回收
-XX:UseCMSInitiatingOccupancyOnly: 表示只在到达阀值的时候, 才进行 CMS 回收
-XX:+CMSIncrementalMode: 设置为增量模式. 适用于单 CPU 情况
-XX:ParallelCMSThreads: 设定 CMS 的线程数量
-XX:CMSInitiatingOccupancyFraction: 设置 CMS 收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection: 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片的整理
G1 收集器设置
-XX:+UseG1GC: 使用 G1 收集器
-XX:ParallelGCThreads: 指定 GC 工作的线程数量
-XX:G1HeapRegionSize: 指定分区大小(1MB~32MB, 且必须是 2 的幂), 默认将整堆划分为 2048 个分区
-XX:GCTimeRatio: 吞吐量大小, 0-100 的整数 (默认 9), 值为 n 则系统将花费不超过 1/(1+n) 的时间用于垃圾收集
-XX:MaxGCPauseMillis: 目标暂停时间(默认 200ms)
-XX:G1NewSizePercent: 新生代内存初始空间(默认整堆 5%)
-XX:G1MaxNewSizePercent: 新生代内存最大空间
-XX:TargetSurvivorRatio:Survivor 填充容量(默认 50%)
-XX:MaxTenuringThreshold: 最大任期阈值(默认 15)
-XX:InitiatingHeapOccupancyPercen: 老年代占用空间超过整堆比 IHOP 阈值(默认 45%), 超过则执行混合收集
-XX:G1HeapWastePercent: 堆废物百分比(默认 5%)
-XX:G1MixedGCCountTarget: 参数混合周期的最大总次数(默认 8)
总结
前面写了一堆的概念, 毕竟概念还是要熟悉一下的, 所以就总结了一下. 其实调优主要还是根据一些个人经验进行不断的测试, 如果感兴趣的小伙伴可以去多试试这些参数看看效果.
来源: http://www.bubuko.com/infodetail-3314651.html