# 九、调优

# 1. 调什么

# 内存方面
  • JVM 需要的内存总大小。
  • 各块内存分配,新生代,老年代,存活区。
  • 选择合适的 GC 算法、控制 GC 停顿次数和时间。
  • 解决内存泄露的问题,辅助代码优化。
  • 内存热点:检查哪些对象在系统中数量最大,辅助代码优化。
# 线程方面
  • 死锁检查,辅助代码优化
  • Dump 线程详细信息:查看线程内部运行情况,查找竞争线程,辅助代码优化。
  • CPU 热点:检查系统哪些方法占用了大量 CPU 时间,辅助代码优化。

# 2. 如何调

  • 监控 JVM 的状态,主要是内存、线程、代码、I/O 几部分。
  • 分析结果,判断是否需要优化。
  • 调整:GC 算法和内存分配;修改并优化代码。

# 3. 调优目标

  • GC 的时间足够的小。
  • GC 的次数足够的少。
  • 将转移到老年代的对象数量降低到最小。
  • 减少 Full GC 的执行时间。
  • 发生 Full GC 的间隔足够长。

# 4. 调优策略

  • 减少创建对象的数量。
  • 减少使用全局变量和大对象。
  • 调整新生代、老年代的大小到最合适。
  • 选择合适的 GC 收集器,并设置合理的参数。

# 5. 调优经验

  • 多数的 Java 应用不需要在服务器上进行 GC 优化。
  • 多数导致 GC 问题的 Java 应用,都不是因为参数设置错误,而是代码问题。
  • 在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合)。
  • JVM 优化是到最后不得已才采用的手段。
  • 在实际使用中,分析 JVM 情况优化代码比优化 JVM 本身要多得多。
  • 如下情况通过不用优化:
    • Minor GC 执行时间不到 50ms
    • Minor GC 执行不频繁,约 10s 一次
    • Full GC 执行时间不到 1s
    • Full GC 执行频率不算高,不低于 10min 一次
  • 要注意 32 位和 64 位的区别,通常 32 位的仅支持 2-3G 左右的内存。
  • 要注意 client 模式和 server 模式的选择
    • client 特点:快速启动,内存占有小,快速代码生成
    • server:更复杂的代码深层优化
  • 想要 GC 时间小必须要一个更小的堆,而要保证 GC 次数足够少,又必须保证一个更大的堆,这两个是冲突的,只能取其平衡。
  • 针对 JVM 堆的设置,一般可以通过 -Xms -Xmx 限定其最小、最大值,为了防止 GC 在最小、最大之间收缩堆而产生额外的时间,通过把最大、最小设置为相同的值。
  • 新生代和老年代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比例 NewRadio 来调整,也可以通过 -XX:newSize-XX:MaxNewSize 来设置其绝对大小。同样,为了防止新生的堆收缩,通常会把 -XX:newSize-XX:MaxNewSize 设置为同样大小。
  • 合理规划新生代和老年代的大小。
  • 如果应用存在大量的临时对象,应该选择更大的新生代;如果存在相对较多的持久对象,老年代应该适当增大。在抉择的时候应该本着 Full GC 尽量少的原则,让老年代尽量缓存常用对象,JVM 的默认比例 1:2 也是这个道理。
  • 通过观察应用一段时间,看其在峰值时老年代会占多少内存,在不影响 Full GC 的前提下,根据实际情况加大新生代,但应该给老年代至少预留 1/3 的增长空间。
  • 线程堆栈的设置:每个线程默认会开启 1M 的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太大了,一般 256K 就足够了。在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程。

# 6. 内存泄漏

# 现象
  • 每次 GC 时间越来越长,Full GC 时间也延长到好几秒。
  • FullGC 的次数越来越多,最频繁时隔不到 1min 就进行一次 Full GC。
  • 老年代的内存越来越大,并且每次 Full GC 后老年代没有内存被释放。
  • 老年代堆空间被占满(OOM)
  • 堆栈溢出(StackOverFlowError)
# 解决
  • 根据垃圾回收前后情况对比,同时根据对象引用情况分析,辅助去查找泄漏点。
  • 堆栈溢出的话要检查递归出口和循环终止条件。
上次更新: 8/27/2021, 6:21:27 PM