跳至主要內容

JVM调优

摸鱼散人大约 22 分钟

有哪些常用的命令行性能监控和故障处理工具?

操作系统工具

工具名 称描述
top实时查看系统的整体性能情况,包括CPU使用率、内存使用情况、 进程信息等
vmstat监控系统的虚拟内存、进程、CPU、I/O等信息,提供实时的性能指 标
iostat监控系统的磁盘I/O性能,包括磁盘读写速度、I/O等待时间等
netstat监控网络连接和网络接口的状态,查看网络连接数、监听端口等信 息
sar收集系统的性能数据,包括CPU、内存、磁盘、网络等指标,可以 生成报告和图表
ps显示系统中的进程信息,包括进程ID、CPU使用率、内存使用情况 等
ifconfig查看和配置网络接口的信息,包括IP地址、子网掩码、网关等
tcpdump抓取网络数据包,用于网络故障排查和分析
lsof列出系统中打开的文件和网络连接,用于查看进程使用的文件和网络资源
htop类似于top,但提供更多的交互式功能和信息展示

JDK工具

工具名 称描述
jps查看Java虚拟机中的进程信息
jstat监控Java虚拟机的运行情况,查看堆内存、垃圾收集、类加载等信 息
jinfo查看Java虚拟机的配置信息
jmap导出Java虚拟机中的内存映像
jhat分析Java虚拟机中的堆转储快照
jstack查看Java虚拟机中的线程堆栈信息
jcmd执行各种诊断命令,包括jstat、jinfo、jmap、jstack等

Arthas工具

工具名称描述
dashboard显示Arthas的仪表盘,包括各种性能指标、JVM参数等
help显示Arthas的帮助文档
jad反编译Java类
jvm显示JVM的信息,包括内存、线程、GC等
logger控制日志输出,包括日志级别、日志文件等
monitor监控方法的调用情况,包括调用次数、耗时等
ognl执行OGNL表达式,用于动态修改变量值等
redefine重新定义类,用于实现热部署
sc查看和修改类的信息,包括类的属性、方法等
thread显示线程的信息,包括线程ID、状态、堆栈等
trace跟踪方法的调用情况,包括方法入参、返回值等

了解哪些可视化的性能监控和故障处理工具?

在Java开发和运维过程中,除了命令行工具之外,还有许多可视化工具可以用于性能监控和故障处理。这些工具提供了图形界面,使得分析和诊断性能问题更加直观和方便。以下是一些常用的可视化性能监控和故障处理工具:

1. VisualVM

  • 主要用途:监控和分析JVM性能,包括内存使用、GC活动、线程状态等。
  • 特点:支持本地和远程监控,插件扩展功能丰富。
  • 官网VisualVMopen in new window

2. JConsole

  • 主要用途:监控JVM性能和资源使用情况,实时查看和管理JMX(Java Management Extensions)MBean。
  • 特点:Java自带工具,易于使用,适合基础监控。
  • 启动命令:在命令行输入 jconsole

3. Java Mission Control (JMC)

  • 主要用途:监控和分析Java应用的性能和运行时行为,特别是与Java Flight Recorder (JFR)配合使用。
  • 特点:提供详细的性能分析和诊断功能,适合长期监控和深度分析。
  • 官网Java Mission Controlopen in new window

4. VisualVM (Profiler)

  • 主要用途:详细分析应用程序性能,找出热点代码和性能瓶颈。
  • 特点:可用于CPU和内存分析,提供可视化的性能报告。
  • 官网VisualVMopen in new window

5. Grafana

  • 主要用途:数据可视化平台,可用来监控各种系统和应用指标。
  • 特点:支持多种数据源,灵活的仪表板,适合大规模监控。
  • 官网Grafanaopen in new window

6. Prometheus

  • 主要用途:开源监控系统和时间序列数据库,适用于系统和服务的监控。
  • 特点:强大的数据收集和查询能力,与Grafana集成效果更佳。
  • 官网Prometheusopen in new window

7. Elastic Stack (ELK Stack)

  • 主要用途:日志和数据分析平台,包含Elasticsearch、Logstash和Kibana。
  • 特点:强大的日志搜索和分析能力,Kibana提供丰富的可视化支持。
  • 官网Elastic Stackopen in new window

8. New Relic

  • 主要用途:应用性能管理(APM)工具,用于监控和优化应用性能。
  • 特点:提供详细的事务跟踪、错误监控、用户体验分析等。
  • 官网New Relicopen in new window

9. AppDynamics

  • 主要用途:应用性能管理和业务监控工具,帮助识别性能问题和优化应用。
  • 特点:提供端到端的性能监控、深度诊断和业务影响分析。
  • 官网AppDynamicsopen in new window

10. Dynatrace

  • 主要用途:全栈监控解决方案,覆盖基础设施、应用、用户体验等。
  • 特点:自动化监控、智能问题检测和根因分析,支持大规模环境。
  • 官网Dynatraceopen in new window

11. Glowroot

  • 主要用途:开源的Java APM工具,监控和诊断Java应用的性能问题。
  • 特点:轻量级、易于部署,提供详细的性能报告。
  • 官网Glowrootopen in new window

12. Pinpoint

  • 主要用途:开源的APM工具,适用于大规模分布式系统的监控。
  • 特点:强大的分布式追踪能力,提供全链路的性能监控。
  • 官网Pinpointopen in new window

总结表格

工具主要用途特点官网
VisualVMJVM性能监控和分析支持本地和远程监控,插件扩展丰富VisualVMopen in new window
JConsoleJVM性能和资源使用监控Java自带工具,易于使用N/A
Java Mission ControlJVM性能和运行时行为分析与Java Flight Recorder (JFR)配合使用,适合深度分析JMCopen in new window
Grafana数据可视化平台支持多种数据源,灵活的仪表板Grafanaopen in new window
Prometheus系统和服务的监控强大的数据收集和查询能力,与Grafana集成效果更佳Prometheusopen in new window
Elastic Stack日志和数据分析平台强大的日志搜索和分析能力,Kibana提供丰富的可视化支持Elastic Stackopen in new window
New Relic应用性能管理(APM)详细的事务跟踪、错误监控、用户体验分析New Relicopen in new window
AppDynamics应用性能管理和业务监控端到端性能监控、深度诊断、业务影响分析AppDynamicsopen in new window
Dynatrace全栈监控解决方案自动化监控、智能问题检测和根因分析,支持大规模环境Dynatraceopen in new window
Glowroot开源的Java APM工具轻量级、易于部署,提供详细的性能报告Glowrootopen in new window
Pinpoint开源的APM工具,适用于大规模分布式系统的监控强大的分布式追踪能力,提供全链路性能监控Pinpointopen in new window

这些可视化工具可以大大简化性能监控和故障处理的工作,使得开发人员和系统管理员能够更直观地理解系统的运行状况,并快速定位和解决问题。根据具体需求,可以选择合适的工具进行使用。

JVM的常见参数配置知道哪些?

Java虚拟机(JVM)的参数配置非常重要,可以显著影响Java应用程序的性能和行为。下面是一些常见的JVM参数配置:

内存管理参数

  1. -Xms: 设置JVM初始化内存堆大小,例如-Xms512m
  2. -Xmx: 设置JVM最大内存堆大小,例如-Xmx1024m
  3. -Xmn: 设置年轻代大小,例如-Xmn256m
  4. -XX:PermSize: 设置初始永久代大小(在Java 8及之前使用),例如-XX:PermSize=128m
  5. -XX:MaxPermSize: 设置最大永久代大小(在Java 8及之前使用),例如-XX:MaxPermSize=256m
  6. -XX:MetaspaceSize: 设置初始元空间大小(从Java 8开始使用),例如-XX:MetaspaceSize=128m
  7. -XX:MaxMetaspaceSize: 设置最大元空间大小,例如-XX:MaxMetaspaceSize=256m

垃圾回收参数

  1. -XX:+UseSerialGC: 使用串行垃圾回收器。
  2. -XX:+UseParallelGC: 使用并行垃圾回收器(也称为吞吐量垃圾回收器)。
  3. -XX:+UseConcMarkSweepGC: 使用并发标记-清除垃圾回收器(CMS)。
  4. -XX:+UseG1GC: 使用G1垃圾回收器。
  5. -XX:ParallelGCThreads: 设置用于并行垃圾回收的线程数,例如-XX:ParallelGCThreads=4
  6. -XX:ConcGCThreads: 设置用于并发标记-清除垃圾回收的线程数。
  7. -XX:InitiatingHeapOccupancyPercent: 设置触发垃圾回收的堆内存占用阈值,例如-XX:InitiatingHeapOccupancyPercent=45

性能调优参数

  1. -XX:+AggressiveOpts: 开启JVM的性能优化选项。
  2. -XX:+UseCompressedOops: 使用压缩指针来减少64位环境下的内存占用。
  3. -XX:+OptimizeStringConcat: 启用字符串连接优化。
  4. -XX:MaxInlineSize: 设置内联方法的最大字节码大小。
  5. -XX:+TieredCompilation: 启用分层编译模式。

调试和监控参数

  1. -Xdebug: 启用调试模式。
  2. -Xrunjdwp: 配置远程调试参数,例如-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
  3. -XX:+PrintGCDetails: 打印详细的垃圾回收日志。
  4. -XX:+PrintGCDateStamps: 在垃圾回收日志中包含时间戳。
  5. -Xloggc: 将垃圾回收日志输出到指定文件,例如-Xloggc:/path/to/gc.log

其他常见参数

  1. -Dproperty=value: 设置系统属性,例如-Dfile.encoding=UTF-8
  2. -server: 启用服务器模式,以获得更高的性能(默认为客户端模式)。

这些参数可以根据具体的应用需求进行调整,以优化JVM的性能和资源利用率。调整时需要通过实际的测试和监控来确定最佳配置。

有做过JVM调优吗?

JVM调优是一个系统性的过程,需要经过多个步骤来确保最佳结果。下面是一个典型的JVM调优流程:

1. 收集信息和分析

  1. 收集应用程序信息:了解应用程序的特性、运行环境和性能需求,包括应用程序的内存使用情况、GC日志、CPU占用率等。
  2. 收集JVM信息:收集JVM的配置信息、垃圾回收器日志、堆内存使用情况等。

2. 设置基准线

  1. 制定性能指标:明确目标性能指标,例如响应时间、吞吐量、垃圾回收停顿时间等。
  2. 收集基准数据:在当前配置下运行应用程序并收集性能数据,作为后续调优的基准。

3. 分析问题和瓶颈

  1. 识别性能问题:分析收集的数据,识别性能瓶颈和问题,例如内存泄漏、频繁的垃圾回收、长时间的垃圾回收停顿等。
  2. 确定调优重点:根据性能问题的严重程度和影响范围确定调优的重点和优先级。

4. 制定调优策略

  1. 选择垃圾回收器:根据应用程序的特性和性能需求选择合适的垃圾回收器。
  2. 调整内存参数:根据应用程序的内存使用情况和性能需求调整堆大小、年轻代大小、永久代(或元空间)大小等内存参数。
  3. 优化垃圾回收策略:根据应用程序的内存使用模式和性能需求调整垃圾回收器的参数,例如调整触发垃圾回收的阈值、并发线程数等。
  4. 优化代码和算法:通过优化Java代码和算法来减少内存使用和垃圾回收的压力。

5. 实施调优和测试

  1. 调整配置:根据制定的调优策略修改JVM的配置参数。
  2. 测试验证:在测试环境下运行调优后的应用程序并收集性能数据,验证调优效果是否达到预期的目标性能指标。

6. 监控和优化

  1. 持续监控:持续监控应用程序的性能和运行状态,及时发现和解决性能问题。
  2. 迭代优化:根据监控数据和反馈信息,持续优化应用程序和JVM的配置,以确保持续稳定的性能表现。

JVM调优是一个迭代和持续改进的过程,需要综合考虑应用程序的特性、性能需求和硬件环境,通过系统性的分析和优化来达到最佳的性能和资源利用率。

有实际调优过吗?

这是一个中规中矩的案例:电商公司的运营后台系统,偶发性的引发OOM异常,堆内存溢出。

  1. 因为是偶发性的,所以第一次简单的认为就是堆内存不足导致,单方面的加大了堆内存从4G调整到8G -Xms8g。
  2. 但是问题依然没有解决,只能从堆内存信息下手,通过开启了-XX:+HeapDumpOnOutOfMemoryError参数 获得堆内存的dump文件。
  3. 用JProfiler 对 堆dump文件进行分析,通过JProfiler查看到占用内存最大的对象是String对象,本来想跟踪着String对象找到其引用的地方,但dump文件太大,跟踪进去的时候总是卡死,而String对象占用比较多也比较正常,最开始也没有认定就是这里的问题,于是就从线程信息里面找突破点。
  4. 通过线程进行分析,先找到了几个正在运行的业务线程,然后逐一跟进业务线程看了下代码,有个方法引起了我的注意,导出订单信息。
  5. 因为订单信息导出这个方法可能会有几万的数据量,首先要从数据库里面查询出来订单信息,然后把订单信息生成excel,这个过程会产生大量的String对象。
  6. 为了验证自己的猜想,于是准备登录后台去测试下,结果在测试的过程中发现导出订单的按钮前端居然没有做点击后按钮置灰交互事件,后端也没有做防止重复提交,因为导出订单数据本来就非常慢,使用的人员可能发现点击后很久后页面都没反应,然后就一直点,结果就大量的请求进入到后台,堆内存产生了大量的订单对象和EXCEL对象,而且方法执行非常慢,导致这一段时间内这些对象都无法被回收,所以最终导致内存溢出。
  7. 知道了问题就容易解决了,最终没有调整任何JVM参数,只是做了两个处理:
  • 在前端的导出订单按钮上加上了置灰状态,等后端响应之后按钮才可以进行点击
  • 后端代码加分布式锁,做防重处理

这样双管齐下,保证导出的请求不会一直打到服务端,问题解决!

线上服务CPU占用过高怎么排查?

  1. 首先,使用top命令列出系统各个进程的资源占用情况

    top
    

    通过观察CPU占用率较高的进程,可以初步确定占用CPU资源较多的进程。

  2. 然后,使用top命令的-H选项,列出指定进程的线程占用资源情况

    top -H -p <进程ID>
    

    通过观察线程的CPU占用率,可以确定占用CPU资源较多的线程。

  3. 找到对应线程ID后,可以使用printf命令将线程ID转换为16进制格式

    printf "%x\n" <线程ID>
    

    将线程ID转换为16进制格式,以便在后续步骤中查找对应的线程信息

  4. 最后,使用jstack命令打印出进程的所有线程信息,并查找转换为16进制的线程ID对应的线程信息

    jstack <进程ID> | grep <转换后的线程ID>
    

    通过分析线程的堆栈信息,可以定位到具体的业务方法,并从代码逻辑中找到问题所在

内存飙高问题怎么排查?

排查内存飙高问题的步骤如下

  1. 观察垃圾回收情况: 使用jstat命令查看垃圾回收的次数、时间等信息,每隔一秒打印一次

    jstat -gc <进程ID> 1000
    

    通过观察GC次数和回收的内存空间,可以初步判断内存飙高的原因

  2. 查看堆内存占用空间最大的对象类型: 使用jmap命令查看堆内存中占用空间最大的前20个对象类型

    jmap -histo <进程ID> | head -20
    

    通过分析占用内存较多的对象类型,可以初步确定哪些对象占用了大量内存

    • 如果垃圾回收次数频繁且每次回收的内存空间较小,说明内存飙高是由于对象创建速度过快导致
    • 如果每次回收的内存较多,可能存在内存泄漏导致内存无法释放
  3. 导出堆内存文件快照: 使用jmap命令将堆内存信息导出到文件中,以便进行离线分析

    jmap -dump:live,format=b,file=/home/myheapdump.hprof <进程ID>
    

    将堆内存信息保存为hprof格式的文件

  4. 使用工具进行离线分析: 使用可视化工具如VisualVM等,打开导出的堆内存文件快照,通过工具提供的分析功能,查找占用内存较高的对象,并定位到创建该对象的业务代码位置

  5. 从定位到的代码和业务场景中,可以进一步分析和优化问题所在

频繁 minor gc 怎么办?

频繁的Minor GC(Young GC)可能是由于对象的存活时间较长,导致Eden区快速填满,从而触发Minor GC。这可能会影响应用程序的性能和响应时间。以下是一些应对频繁Minor GC的建议

  1. 调整堆内存大小:增加堆内存的大小,可以减少Minor GC的频率。可以通过调整-Xms和-Xmx参数来增加堆内存大小。但是需要注意,过大的堆内存可能会导致GC停顿时间增加,影响系统的响应性能
  2. 优化对象的生命周期:检查应用程序的代码逻辑,尽量减少对象的生命周期,避免对象在Eden区存活时间过长。可以通过合理的对象设计和资源管理来优化对象的生命周期
  3. 检查对象的引用:确保对象的引用关系正确,避免出现无效的引用或者内存泄漏。特别是在使用缓存或者集合等数据结构时,需要注意及时清理不再使用的对象
  4. 调整新生代的比例:可以通过调整新生代(Young Generation)和老年代(OldGeneration)的比例来减少Minor GC的频率。可以通过调整-XX:NewRatio参数来改变新生代和老年代的比例
  5. 使用并行GC或者G1 GC:并行GC和G1 GC在处理Minor GC时,可以利用多线程并行处理,提高GC的效率。可以通过调整-XX:+UseParallelGC或者-XX:+UseG1GC参数来选择使用并行GC或者G1 GC
  6. 使用逃逸分析和标量替换:逃逸分析可以分析对象的作用域,确定对象是否可以在栈上分配,从而减少堆内存的使用。标量替换可以将一个对象拆分为多个独立的标量,减少对象的内存占用。可以通过启用逃逸分析和标量替换来优化对象的内存分配
  7. 监控和分析GC日志:通过监控和分析GC日志,可以了解GC的频率、停顿时间和内存使用情况,从而找到优化的方向。可以使用工具(如GCViewer、GCEasy等)来分析GC日志

频繁Full GC怎么办?

Full GC的排查思路大概如下

  1. 清楚从程序角度,有哪些原因导致FGC

    1. 大对象 :系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。
    2. 内存泄漏 :频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发FGC,最后导致OOM.
    3. 程序频繁生成一些 长生命周期的对象 ,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发FGC. (即本文中的案例)
    4. 程序BUG
    5. 代码中 显式调用了gc 方法,包括自己的代码甚至框架中的代码。
    6. JVM参数设置问题:包括总内存大小、新生代和老年代的大小、Eden区和S区的大小、元空间大小、垃圾回收算法等等
  2. 清楚排查问题时能使用哪些工具

    1. 公司的监控系统:大部分公司都会有,可全方位监控JVM的各项指标

    2. JDK的自带工具,包括jmap、jstat等常用命令

      # 查看堆内存各区域的使用率以及GC情况
      jstat -gcutil -h20 pid 1000
      # 查看堆内存中的存活对象,并按空间排序
      jmap -histo pid | head -n20
      # dump堆内存文件
      jmap -dump:format=b,file=heap pid
      
    3. 可视化的堆内存分析工具:JVisualVM、MAT等

  3. 排查指南

    1. 查看监控,以了解出现问题的时间点以及当前FGC的频率(可对比正常情况看频率是否正常)
    2. 了解该时间点之前有没有程序上线、基础组件升级等情况。
    3. 了解JVM的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃圾收集器,然后分析JVM参数设置是否合理。
    4. 再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。
    5. 针对大对象或者长生命周期对象导致的FGC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象
    6. 通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论

有没有处理过内存泄漏问题?是如何定位的?

内存泄漏是内在病源,外在病症表现可能有:

  • 应用程序长时间连续运行时性能严重下降
  • CPU 使用率飙升,甚至到 100%
  • 频繁 Full GC,各种报警,例如接口超时报警等
  • 应用程序抛出 OutOfMemoryError 错误
  • 应用程序偶尔会耗尽连接对象

严重内存泄漏往往伴随频繁的 Full GC,所以分析排查内存泄漏问题首先还得从查看 Full GC 入手。主要有以下操作步骤

  1. 使用 jps 查看运行的 Java 进程 ID

  2. 使用 top -p [pid] 查看进程使用 CPU 和 MEM 的情况

  3. 使用 top -Hp [pid] 查看进程下的所有线程占 CPU 和 MEM 的情况

  4. 将线程 ID 转换为 16 进制: printf "%x\n" [pid] ,输出的值就是线程栈信息中的 nid。

    1. 例如: printf "%x\n" 29471 ,换行输出 731f。

    2. 在线程栈信息中找到对应线程号的 16 进制值,如下是 731f 线程的信息。线程栈分析可使用 Visualvm 插件 TDA

      "Service Thread" #7 daemon prio=9 os_prio=0
      tid=0x00007fbe2c164000 nid=0x731f runnable
      [0x0000000000000000]
      java.lang.Thread.State: RUNNABLE
      
  5. 抓取线程栈: jstack 29452 > 29452.txt ,可以多抓几次做个对比。

  6. 使用 jstat -gcutil [pid] 5000 10 每隔 5 秒输出 GC 信息,输出 10 次,查看 YGC 和 Full GC 次数。通常会出现 YGC 不增加或增加缓慢,而 Full GC增加很快。或使用 jstat -gccause [pid] 5000 ,同样是输出 GC 摘要信息 或使用 jmap -heap [pid] 查看堆的摘要信息,关注老年代内存使用是否达到阀值,若达到阀值就会执行 Full GC

  7. 如果发现 Full GC 次数太多,就很大概率存在内存泄漏了

  8. 使用 jmap -histo:live [pid] 输出每个类的对象数量,内存大小(字节单位)及全限定类名

  9. 生成 dump 文件,借助工具分析哪 个对象非常多,基本就能定位到问题在那了使用 jmap 生成 dump 文件

    # jmap -dump:live,format=b,file=29471.dump 29471
    Dumping heap to /root/dump ...
    Heap dump file created
    
  10. dump 文件分析

    1. 可以使用 jhat 命令分析: jhat -port 8000 29471.dump ,浏览器访问 jhat服务,端口是 8000。
    2. 通常使用图形化工具分析,如 JDK 自带的 jvisualvm,从菜单 > 文件 > 装入dump 文件。
    3. 或使用第三方式具分析的,如 JProfiler 也是个图形化工具,GCViewer 工具。Eclipse 或以使用 MAT 工具查看。或使用在线分析平台 GCEasy。
    4. 注意:如果 dump 文件较大的话,分析会占比较大的内存
  11. 在 dump 文析结果中查找存在大量的对象,再查对其的引用

有没有处理过OOM问题?

  1. 查看错误日志

    1. 首先,查看应用程序的错误日志,以查找包含OOM异常信息的记录。通常,OOM异常会被明确标记,例如"OutOfMemoryError"
  2. 分析堆栈信息

    1. 在错误日志中找到OOM异常的堆栈跟踪信息。这将指出在哪个方法和代码位置发生了问题。通常,你会看到类似以下的信息

      Exception in thread "main" java.lang.OutOfMemoryError:
      Java heap space
      at com.example.MyClass.myMethod(MyClass.java:12)
      
    2. 根据堆栈信息,确定哪个方法或代码段导致了OOM异常

  3. 内存分析工具

    1. 使用内存分析工具(如MAT、VisualVM等)来检查应用程序的内存使用情况。以下是使用VisualVM的一般步骤
      1. 启动VisualVM,并连接到运行中的Java进程
      2. 在VisualVM的"内存"选项卡下,查看堆内存使用情况、对象实例数等信息
      3. 分析堆转储文件以查找内存泄漏或异常的对象
  4. 检查代码

    1. 根据堆栈信息定位到可能的问题代码段。检查这些代码段是否存在内存泄漏或不合理的内存使用
    2. 确保对象在不再需要时被垃圾回收,避免不必要的对象引用