调什么

JVM调优:1、是想调什么,2、能调什么(只能调开放接口的,很多东西不一定开放接口给你调整),综合考虑只有如下两个方面:

  • 内存方面
  • 线程方面

内存方面

  • JVM需要的内存总大小。

  • 各块内存分配,新生代、存活区、老年代。

  • 选择合适的垃圾回收算法、控制GC停顿次数和时间。

  • 解决内存泄漏的问题,辅助代码优化。

  • 内存热点:检查哪些对象在系统中数量最大,辅助代码优化。

线程方面

  • 死锁检查,辅助代码优化。

  • Dump线程详细信息:查看线程内部运行情况,查找竞争线程,辅助代码优化。

  • CPU热点:检查系统哪些方法占用了大量CPU时间,辅助代码优化。

如何调优

  • 监控JVM的状态,主要是内存、线程、代码、I/O几部分。

  • 分析结果,判断是否需要优化。

  • 调整:垃圾回收算法和内存分配,修改并优化代码。

  • 不断的重复监控、分析和调整,直至找到优化的平衡点。

JVM调优的目标

  • GC的时间足够小。

  • GC的次数足够小。

  • 将转移到老年代的对象数量降低到最小。

  • 减少Full GC的执行时间。

  • 发生Full GC的间隔时间足够的长。

常见的调优策略

  • 减少创建对象的数量。

  • 减少使用全局变量和大对象。

  • 调整新生代、老年代的大小到最合适。

  • 选择合适的GC收集器,并设置合理的参数。

JVM调优冷思考

  • 多数的Java应用不需要在服务器上进行GC优化。

  • 多数导致GC问题的Java应用,都不是因为参数设置错误,而是代码问题。

  • 在应用上线前,先考虑将机器的JVM参数设置到最优(最合适)。

  • JVM优化是到最后不得已才采用的手段。

  • 在实例使用中,分析JVM情况优化代码比优化JVM本身要多得多。

  • 如下情况通常不用优化:

    • Minor GC执行时间不到50ms。
    • Minor GC执行不频繁,约10秒甚至更差时间才执行一次。
    • Full GC执行时间不到1s。
    • Full GC执行频率不算频繁,不低于10分钟1次。

JVM调优经验

  • 要注意32位和64为操作系统的区别,32位操作系统支持的内存理论上最多为2的32次方4G,而64位操作系统理论的寻址能力为2的64次方,内存识别的多少是和计算机cpu的寻址有关。

  • 注意client模式和Server模式的选择。

  • 要想GC的时间小必须要一个更小的堆,而要保证GC次数足够少,又必须要保证一个更大的堆,这两个是有冲突的,只能取其平衡。

  • 针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值。

  • 新生代和老年代将根据默认的比例(1:2)分配内存堆,可以通过调整二者之间的比率NewRadio来调整,也可以通过-XX:newSize -XX:MaxNewSize来设置其绝对大小,同样,为了防止新生的堆收缩,通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。

  • 合理规划新生代和老年代的大小。

  • 如果应用存在大量的临时对象,应该选择更大的新生代;如果存在相对较多的持久对象,老年代应该适当增大。在抉择时应该本着Full GC尽量少的原则,让老年代尽量缓存常用对象,JVM的默认比例1:2也是这个道理。

  • 通过观察应用一段时间,看其在峰值时老年代会占多少内存,在不影响Full GC的前提下,根据实际情况加大新生代,但应该给老年代至少预留1/3的增长空间。

  • 线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太大了,一般256K就足够了。在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程。

内存泄漏

  • 内存泄漏导致系统崩溃前的一些现象,比如:

    • 1、每次垃圾回收时间越来越长,Full GC时间也延长到好几秒。
    • 2、Full GC的次数越来越多,最频繁时隔不到1分钟就进行一次Full GC。
    • 3、老年代的内存越来越大,并且每次Full GC后老年代没有内存被释放。
    • 4、老年代堆空间被占满的情况。
  • 内存泄漏的解决方式:一般就是根据垃圾回收前后情况对比,同时根据对象引用情况分析,辅助去查找泄漏点。

  • 堆栈溢出的情况,通常会抛出java.lang.StackOverflowError,一般就是递归调用没退出,或者循环调用导致。

调优步骤

重点是调优的过程、方法和思路

内存调整、数据库连接调整、内存泄漏查找等

通过JMC录制JFR飞行记录

  • 1、查看CPU的占用率

  • 2、内存的分配和使用量

  • 3、垃圾收集的执行频率、暂停时间、垃圾收集区域

  • 4、文件I/O、套接字I/O,查看远程I/O阻塞时间

  • 一般信息,堆使用量、CPU总体占用率、GC暂停时间是非常重要的三个指标,对于Java应用而言,GC暂停时间是最值得关注的指标。

  • 通过内存信息,我们可以清晰的看到垃圾收集器的类型,垃圾收集的暂停时间,包括最短暂停时间、平均暂停时间、最长暂停时间,以及更为重要的垃圾收集频率(垃圾收集的周期及STW时长)。

  • 代码分析是Java性能分析重点,通过代码分析,我们可以清楚的知道系统运行时,哪些类及方法被高频率的调用。

  • 热点方法,通过查看热点方法调用栈,我们可以清晰的了解到系统的主要计算资源消耗情况。

  • 通过调用树,我们能以模块化的方式直观的看到系统运行状态。

  • 通过线程概述报告,我们可以得知CPU占用率的分布(系统占用率、应用程序+JVM占用率)和活动线程数,对于CPU占用率而言,应用程序应该占用99%的计算资源,而活动线程数应该控制在合理范围内(具体看应用)。

  • 热点线程一栏,详细列出了热点线程的数量及详情,通过详情,我们可以得知线程的执行情况。

  • 线程争用是解决应用性能最为关键的部分,在应用上线初期,我们可以通过解决线程争用初步实现系统性能的巨大提升。上图中的争用为GC导致,具体是由于使用G1时,设置的GC预期暂停时间过短导致的。

  • IO作为系统的基础指标,IO过高会导致系统性能急剧下降,避免过度打印日志和生成大文件可以避免系统IO过高导致的性能问题。

通过VisualVM查询实时的虚拟机信息

  • 1、查看监视界面,可以看到cup运行情况、堆的使用情况、类的情况以及线程的动态情况。

  • 2、查看线程,可以看到所有的线程的情况,是运行、休眠、等待、驻留、监视等情况。

也可以点击右上角Dump按钮,将线程的信息导出,其实就是执行的jstack命令。

  • 3、查看抽样器,抽样器可以对CPU、内存在一段时间内进行抽样,
    • A、CPU监控:查看热点方法和各个方法占用CPU时间及其比例。
    • B、内存监控:每个线程分配内存。

抽样cpu

  • 4、添加插件Visual GC,利用Visual GC分析虚拟机内存区域。

    • A、内存大小分情况
    • B、主要关注 GC Time长短及间隔
    • C、查看是否old Gen,Eden是否存在持续上升
  • 5、添加插件Tracer,Tracer可以监控Heap、PerGen、Cleass、Treads等等

总结:

  • 通过JMC录制JFR飞行记录和通过VisualVM监控实时的虚拟机信息,进行调整优化。

  • 针对性的去调整JVM内存、新生代、年代的空间大小、数据库连接池大小、MySQL最大连接数等。

  • 不断的分析和调整,直到找到合适的JVM参数配置,找到最合适的参数,将这些参数应用到所有服务器,并进行后续跟踪。

参考: https://www.cnblogs.com/xifengxiaoma/p/9402263.html

https://my.oschina.net/yygh/blog/650507

https://www.cnblogs.com/xifengxiaoma/p/9402497.html

https://zhuanlan.zhihu.com/p/42541651