6

Java 虚拟机(JVM)调优思路

 2 years ago
source link: https://tianmingxing.com/2022/04/09/Java%E8%99%9A%E6%8B%9F%E6%9C%BA%EF%BC%88JVM%EF%BC%89%E8%B0%83%E4%BC%98%E6%80%9D%E8%B7%AF/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
  1. JVM 需要的内存总大小
  2. 各块内存分配,新生代、老年代、存活区
  3. 选择合适的垃圾回收算法、控制 GC 停顿次数和时间
  4. 解决内存泄露的问题,辅助代码优化
  5. 内存热点:检查哪些对象在系统中数量最大,辅助代码优化
  1. 死锁检查,辅助代码优化
  2. Dump 线程详细信息:查看线程内部运行情况,查找竞争线程,辅助代码优化
  3. CPU 热点:检查系统哪些方法占用的大量 CPU 时间,辅助代码优化
  1. 监控 JVM 的状态,主要是内存、线程、代码、I/O 几个部分
  2. 分析结果,判断是否需要优化
  3. 调整 GC 类型和内存分配;修改并优化代码
  4. 不断的重复分析和调整,直至找到优化的平衡点

JVM 调优的目标

  1. GC 的时间足够的小
  2. GC 的次数足够的少
  3. 将转移到老年代的对象数量降低到最小
  4. 减少 full GC 的执行时间
  5. 发生 Full GC 的间隔足够的长

JVM 调优冷思考

  1. 多数的 Java 应用不需要在服务器上进行 GC 优化
  2. 多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题
  3. 在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合)
  4. GC 优化是到最后不得已才采用的手段
  5. 在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多
  6. 如下情况通常不用优化:
  • Minor GC 执行时间不到 50ms
  • Minor GC 执行不频繁,约 10 秒一次
  • Full GC 执行时间不到 1s
  • Full GC 执行频率不算频繁,不低于 10 分钟 1 次

常见调优策略

  1. 减少创建对象的数量
  2. 减少使用全局变量和大对象
  3. 调整新生代的大小到最合适
  4. 设置老年代的大小为最合适
  5. 选择合适的 GC 收集器
  6. 将转移到老年代的对象数量降到最少
  7. 减少 Full GC 的执行时间
  1. 要注意 32 位和 64 位的区别,通常 32 位的仅支持 2-3g 左右。
  2. 要注意 client 模式和 Server 模式的选择:client runtime 是快速启动,更小的内存占用以及快速代码(机器码)生成的 JIT 编译器。server runtime 有更复杂的代码生成优化,作为服务型应用更为靠谱
  3. 要想 GC 时间小必须要一个更小的堆;而要保证 GC 次数足够少,又必须保证一个更大的堆,这两个是有冲突的,只能取其平衡。
  4. 针对 JVM 堆的设置,一般可以通过 - Xms -Xmx 限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值。
  5. 新生代和老年代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率 NewRadio 来调整二者之间的大小,也可以通过 - XX:newSize -XX:MaxNewSize 来设置其绝对大小,同样,为了防止新生的堆收缩,通常会把 - XX:newSize -XX:MaxNewSize 设置为同样大小。
  6. 更大的新生代必然导致更小的老年代,大的新生代会延长普通 GC 的周期,但会增加每次 GC 的时间;小的老年代会导致更频繁的 Full GC;更小的新生代必然导致更大老年代,小的新生代会导致普通 GC 很频繁,但每次的 GC 时间会更短;大的老年代会减少 Full GC 的频率。
  7. 如果应用存在大量的临时对象,应该选择更大的新生代;如果存在相对较多的持久对象,老年代应该适当增大。在抉择时应该本着 Full GC 尽量少的原则,让老年代尽量缓存常用对象,JVM 的默认比例 1:2 也是这个道理。
  8. 通过观察应用一段时间,看其在峰值时老年代会占多少内存,在不影响 Full GC 的前提下,根据实际情况加大新生代,比如可以把比例控制在 1:1。但应该给老年代至少预留 1/3 的增长空间。
  9. 线程堆栈的设置:每个线程默认会开启 1M 的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般 256K 就足用。在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程。

一:内存泄露导致系统崩溃前的一些现象

  1. 每次垃圾回收的时间越来越长,FullGC 的时间也延长到好几秒
  2. FullGC 的次数越来越多,最频繁时隔不到 1 分钟就进行一次 FullGC
  3. 老年代的内存越来越大,并且每次 FullGC 后年老代没有内存被释放,慢慢的系统会无法响应新的请求,逐渐到达 OutOfMemoryError 的临界值。

二:老年代堆空间被占满的情况

  1. 这是非常典型的内存泄漏的垃圾回收情况,通常监控图上所有峰值部分都是一次垃圾回收点,所有谷底部分表示是一次垃圾回收后剩余的内存。连接所有谷底的点,可以发现一条由底到高的线,这说明随时间的推移,系统的堆空间被不断占满,最终会占满整个堆空间。
  2. 这种情况的解决方式:一般就是根据垃圾回收前后情况对比,同时根据对象引用情况分析,基本都可以找到泄漏点。

三:堆栈溢出的情况

  1. 通常抛出 java.lang.StackOverflowError 例外
  2. 这个一般就是递归调用没退出,或者循环调用造成

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK