一文搞懂:Java项目线上故障处理的基本方法

一. 调优和故障处理的方向

Java调优通常指的是对Java应用程序进行性能优化和资源管理的过程。调优的方向很多也很广,比如:

  • 内存管理:优化Java应用程序的内存使用,包括减少内存泄漏、合理设置堆大小、优化垃圾收集器的选择和参数配置等。
  • 线程管理:通过优化线程使用方式,避免过多线程竞争和线程阻塞,提高多线程应用程序的性能和稳定性。
  • IO操作:优化文件和网络IO操作,避免阻塞和资源浪费,提高IO操作的效率。
  • 数据库访问:优化数据库访问的性能,包括减少数据库连接的创建和关闭、合理利用数据库连接池等。
  • 算法和数据结构:优化算法和数据结构的选择和实现,提高程序的执行效率和资源利用率。
  • JVM调优:通过调整JVM的参数和配置,优化Java应用程序在JVM上的运行性能,包括堆大小、垃圾收集器、JIT编译器等。

本文主要指的是:内存管理线程管理JVM调优 这几个方面。

线上(生产环境)故障指的是在实际运行中出现的影响系统正常功能的问题或错误,这些问题可能导致系统停止响应、性能下降、功能异常、数据丢失等不良后果。人为的代码bug造成的业务bug等,不在本篇中,本篇主要是指比如:OOM异常机器内存耗尽机器CPU飙升访问超时快速定位等等。

写在前面:如果出现问题,最快速的方式就是看日志(前提是能看到),所以建议做好生产环境的日志打印;本篇只是讲一些基本的操作方式,正常环境需要有全面的认知和灵活的应用

所以建议生产环境启动时加上以下参数,免得出问题了找不到日志:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/app/app_oomErr.hprof		//堆转储文件的路径   自定义
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintTenuringDistribution
-XX:+PrintHeapAtGC
-XX:+PrintReferenceGC
-XX:+PrintGCApplicationStoppedTime
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=14
-XX:GCLogFileSize=100M
-Xloggc:/tmp/gc-%t.log   	// GC 日志文件的路径  自定义

二. 常用JDK自带的命令工具介绍

1. jps

jps命令主要用来查看JVM进程状态:

  • jps -m 显示传递给main方法的参数
  • jps -l 显示应用main class的完整包应用的jar文件完整路径名
  • jps -v 显示传递给JVM的参数
    例子:
    在这里插入图片描述
    在这里插入图片描述
    命令也可以组合使用在这里插入图片描述

2. jstat

jstat(JVM Statistics Monitoring Tool) 使用于监视虚拟机各种运行状态信息的命令行工具:

  • jstat -gc <pid>:显示与 GC 相关的堆信息
  • jstat -gccapacity <pid>:显示各个代的容量及使用情况
  • jstat -gcnew <pid>:显示新生代信息
  • jstat -gcnewcapcacity <pid>:显示新生代大小与使用情况
  • jstat -gcold <pid>:显示老年代和永久代的行为统计,从 jdk1.8 开始,该选项仅表示老年代,因为永久代被移除了
  • jstat -gcoldcapacity <pid>:显示老年代的大小
  • jstat -gcutil <pid>:显示垃圾收集信息
  • jstat -class <pid>:显示 ClassLoader 的相关信息
  • jstat -compiler <pid>:显示 JIT 编译的相关信息

<pid> 表示要查看的进程id,就是jps命令中的进程号。例如:
在这里插入图片描述

3. jinfo

jinfo <pid>命令用于输出当前 jvm 进程的全部参数和系统属性,第一部分是系统的属性,第二部分是 JVM 的参数。
jinof -flag <name> :用于指定输出某个参数,name表示参数名字。
例如:
查看对应进程的最大堆内存大小:
在这里插入图片描述
查看线程栈大小:
在这里插入图片描述

4. jmap

jmap(Memory Map for Java)命令用于生成堆转储快照。 如果不使用 jmap 命令,要想获取 Java 堆转储,可以在启动命令中添加 -XX:+HeapDumpOnOutOfMemoryError ,虚拟机会在发生 OOM 异常出现之后自动生成 dump 文件,Linux 命令下可以通过 kill -3 <pid> 发送进程退出信号也能拿到 dump 文件。
栗子:获取2336的Java进程的堆转储文件,jmap -dump:live,format=b,file=appDumpFile.hprof 2336,这个命令中-dump表示连接到正在运行的进程,并转储Java堆;live表示指定时,仅Dump活动对象;如果未指定,则转储堆中的所有对象;format=b表示以hprof格式Dump堆;file=filename表示堆Dump存储的路径和文件名;最后的2336是例子的pid
在这里插入图片描述
如上图所示,会在机器的/root目录下生成一份appDumpFile.hprof文件,这个文件就是2336的Java进程的堆转储文件。如果想控制生成的路径,可以自行修改file=/xxx/xxxx/fileName.hprof

5. jstack

jstack(Stack Trace for Java)命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合.生成线程快照的目的主要是定位线程长时间出现停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做些什么事情,或者在等待些什么资源。
在实际项目中,如果你的应用出现长时间等待的时候,可以考虑使用jstack,比方说:线程死锁死循环远程请求长时间得不到返回等,都可能会出现线程长时间的等待,那么使用jstack可以得到每个线程的调用信息,这样就可以知道那些没有响应的线程在后台到底做什么或者正在等待什么样的资源。

  • jstack <pid> 生成虚拟机当前时刻的线程快照
  • jstack -l <pid> 显示有关锁的额外信息
  • jstack -F <pid> 当正常输出的请求不被响应时,强制输出线程堆栈
  • jstack -m <pid> 如果调用到本地方法的话,可以显示C/C++的堆栈

下面通过一个人为的死锁来演示一下通过jstack命令来查看死锁问题;
先看死锁代码:

public class DeathLockTest {
    private static Lock lock1 = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();
    
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                try {
                    lock1.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock1");
                    Thread.sleep(1000);
                    lock2.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                try {
                    lock2.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock2");
                    Thread.sleep(1000);
                    lock1.lock();
                    System.out.println(Thread.currentThread().getName() + " get the lock1");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.setName("myLockThread-1");
        t2.setName("myLockThread-2");
        t1.start();
        t2.start();
    }
}

由于代码为演示代码,所以我在windows电脑上进行操作:
在这里插入图片描述
首先通过jps命令获取到进程的pid,然后通过jstack <pid>命令查看当前进程的线程信息,在输出信息中可以看到线程的运行状态等各种信息,在后续的输出中可以看到:
在这里插入图片描述
就可以看出来是我们的DeathLockTest类的21行和35行发生了死锁。
这里先演示一下jps命令的方法,本文后续会通过cpu飙升来演示linux的top命令查看故障的方式。

6. jconsole

JConsole 是基于 JMX 的可视化监视、管理工具。可以很方便的监视本地及远程服务器的 java 进程的内存使用情况。
比如我windows本地jdk安装目录在:C:\Program Files\Java\jdk1.8.0_131,那么jconsole.exe就在C:\Program Files\Java\jdk1.8.0_131\bin目录下。
双击jconsole.exe即可打开:
在这里插入图片描述
打开的界面会自动列出本地的java进程,还可以远程连接其他服务器的Java进程。如果你需要远程连接访问,那么需要在被连接的项目启动时加上如下参数:

-Djava.rmi.server.hostname=外网ip地址
-Dcom.sun.management.jmxremote.port=60001   //监控的端口号
-Dcom.sun.management.jmxremote.authenticate=false   //关闭认证
-Dcom.sun.management.jmxremote.ssl=false

在使用JConsole 远程连接时,远程进程地址为:外网ip地址:60001,就是hostname:port
此处以本地服务为例,进入后为:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到JConsole 通过可视化的方式提供了Java程序运行时的各种信息,我们可以查看各种内存使用情况和线程情况,可以手动触发FullGC,可以检查死锁,还可以查看运行时的类加载信息等等,可以有效的帮助我们了解运行时的各种情况。

7. VisualVM

VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随 JDK 发布的功能最强大的运行监视和故障处理程序,官方在 VisualVM 的软件说明中写上了“All-in-One”的描述字样,预示着他除了运行监视、故障处理外,还提供了很多其他方面的功能,如性能分析(Profiling)。VisualVM 的性能分析功能甚至比起 JProfiler、YourKit 等专业且收费的 Profiling 工具都不会逊色多少,而且 VisualVM 还有一个很大的优点:不需要被监视的程序基于特殊 Agent 运行,因此他对应用程序的实际性能的影响很小,使得他可以直接应用在生产环境中。这个优点是 JProfiler、YourKit 等工具无法与之媲美的。

VisualVM 和JConsole 一样都在jdk的bin目录下,双击jvisualvm.exe即可打开。
在这里插入图片描述
可以看到VisualVM 也可以查看Java进程运行时的信息或者执行GC,但是多了一个堆dump功能(仅本地进程可使用,远程进程无此功能),点击之后会生成一份当前Java进程的dump文件:
在这里插入图片描述

这里可以很清楚的看到dump文件中的各种信息,尤其是类信息,当发生OOM等内存问题时,一般都可以在这里看到异常。比如我们发现某个类内存占用特别高或者有异常,那么可以双击这个类,比如上图中圈起来的java.lang.String,就可以详细的查看这个类的所有实例信息:
在这里插入图片描述
通过这个功能可以很方便的让我们查看类的各种实例信息,分析内存溢出、内存泄漏等问题。

个人觉得VisualVM 功能比JConsole 强大,但是单独看内存使用情况的话,JConsole 更方便直观一些。
但是VisualVM 也可以用来分析已有的其他dump文件,比如我们使用jmap命令导出了一份dump.hprof文件,此时我们点击VisualVM 左上角菜单栏的“文件”,选择:“装入”
在这里插入图片描述
在这里插入图片描述
即可打开单独的dump文件查看信息,与上面堆dump功能查看dump文件一样。

8. MAT(MemoryAnalyzer Tool)

MAT(全名:Memory Analyzer Tool),是一款快速便捷且功能强大丰富的 JVM 堆内存离线分析工具。其通过展现 JVM 异常时所记录的运行时堆转储快照(Heap dump)状态(正常运行时也可以做堆转储分析),帮助定位内存泄漏问题或优化大内存消耗逻辑。
下载地址:https://eclipse.dev/mat/previousReleases.php,记着自己的jdk是哪个版本,就下载对应版本号的,比如jdk8的就下载:
在这里插入图片描述
下载后解压,双击文件夹中的MemoryAnalyzer.exe就可以打开了。打开后选择
在这里插入图片描述
可以用来打开我们之前到处的dump文件,打开后如下:
在这里插入图片描述
这个页面展示了dump文件的基本信息统计。
我们常用的功能主要是:HistogramDominator Treethread_overview,Leak Suspects
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • Histogram:列出内存中的对象,对象的个数以及大小。Histogram在类的角度上进行分析,注重量的分析。
  • Dominator Tree:列出最大的对象以及其依赖存活的Object (大小是以Retained Heap为标准排序的),是在对象实例的角度上进行分析,注重引用关系分析。它按对象的 Retain Heap 排序,也支持按多个维度聚类统计。
  • thread_overview:查看当前进程dump时的所有线程的堆栈信息,通过分析堆栈信息可以快速定位到对应的线程所执行的方法等层级关系,以此来定位对应的异常问题
  • Leak Suspects:用于查找内存泄漏问题,以及系统概览

MAT的功能很强大,本篇就不详细讲述了,总之MAT可以帮我们分析:

  • 内存溢出,JVM堆区或方法区放不下存活及待申请的对象。如:高峰期系统出现 OOM(Out of Memory)异常,需定位内存瓶颈点来指导优化。
  • 内存泄漏,不会再使用的对象无法被垃圾回收器回收。如:系统运行一段时间后出现 Full GC,甚至周期性 OOM 后需人工重启解决。
  • 内存占用高,如:系统频繁 GC ,需定位影响服务实时性、稳定性、吞吐能力的原因。
  • 性能优化,如:内存分配调整等等

具体资料大家可以自行搜索MAT相关内容学习。

9. gceasy

这是一个网站:https://gceasy.io,可以使用谷歌或者github账号进行登录,GCeasy 是一个基于 Web 的垃圾回收日志分析工具,可以用于分析 JVM 的垃圾回收日志,提供可视化的分析结果和建议。GCeasy 可以分析所有主流的垃圾回收器,包括 CMS、G1、Parallel 和 Serial 等,支持多种垃圾回收日志格式,包括 GC log、Jstat log 和 JMX 等。
我们在项目启动时,开启GC日志打印,-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/tmp/gc.log,或者使用文章开头的启动参数,就可以获取到GC的日志,然后拿到GC日志之后,在https://gceasy.io导入日志文件:
在这里插入图片描述
稍等一会就可看到分析结果了,这个结果的可视化做的很好,但是注意个人用户一个账号一个月只能免费用5次哈,理论上足够的,实在不行谷歌登录用5次,github登录再用5次,一共10次怎么也够用了。。。然后看分析结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个分析结果中有很多有用的信息,这里就不展开讲了,感兴趣的可以专门去查阅一下分析结果的含义,网上挺多资料的,总之这个分析结果对GC优化很有帮助,而且能生成图表,还能下载成pdf,装逼神器啊!!!。。。给不懂技术的老板看,一看就很高大上有木有?


到这里其实工具介绍的差不多了,问题的解决方案其实应该已经有一个大致的方向和思路了,如果到这里还没有思路,那就说明对Jvm、GC等还没有深刻的认知,你要会用这些工具可不够,还要会看着工具给出的信息分析出相应的处理方法。下面就通过两个例子来简单介绍下如何处理问题吧。

三. OOM了怎么办

例如:某天发现服务出现异常报错,一会报一下一会报一下,或者有阶段性的服务异常,又或者你的服务本身有监控,看到监控中内存飙升,又或者查看异常日志发现有OutOfMemoryError,这个时候很明显就是内存溢出了… …当然异常情况可能有很多种,这里只是举个栗子

如果确定了是OOM异常,那么就看你的项目启动时有没有添加启动参数:

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath

如果加了那就去看-XX:HeapDumpPath后面对应的dump文件,如果按照文章开头给出的例子配置,那么就可以获取到一份发生OOM异常时的hprof 文件。

如果你没有加这个启动参数,那么看下部署项目的机器能不能使用jmap -dump:live,format=b,file=appDumpFile.hprof <pid>命令获取到发生OOM时的内存快照,如果能获取到,那么就下载下来appDumpFile.hprof

通过以上两种方式获取到快照文件后,导入到上面介绍的MAT工具中,然后查看MAT中的HistogramDominator TreeLeak Suspects等,分析内存异常点,定位到代码位置,然后修改问题。
问题修改完成之后,记着修改启动参数加上-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath

四. CPU告警怎么办

例如:某天发现服务器的CPU飙升出现报警,或者接口出现大量超时,日志中没有明显的OOM等异常,也无法从日志中获取有效的信息定位问题点,这种时候可以尝试查看Java进程的线程信息,用来排查是不是出现死锁或者出现了死循环等等…

这个时候就可使用jstack命令,前文中说的是使用jps命令,直接进行jstack查看,这种查看的信息太多无法精准获取有效信息,一般情况下出现CPU飙升,我们会在服务器上使用top命令进行初步查看,获取到占用CPU最高的Java进程的PID(如果是因为Java进程导致的),然后再使用top -Hp <PID>查看Java进程下的所有线程占CPU的情况,然后找到占用CPU最高的问题线程id(tid),再使用printf "%x\n" <tid>命令把问题线程id转为16进制的线程id,然后再通过jstack <pid> | grep <十六进制tid> -A 20查看详细的线程情况,从而定位的具体问题。

简单总结一下:

  1. 执行top命令:查看所有进程占系统CPU的排序。极大可能排第一个的就是的java进程(COMMAND列)。PID那一列就是进程号(pid)。
  2. 执行top -Hp <pid>命令:查看java进程下的所有线程占CPU的情况,得到Java进程中占用CPU最高的问题线程id(tid)。
  3. 执行printf "%x\n" <tid>命令 :后续查看线程堆栈信息展示的都是十六进制,为了找到咱们的线程堆栈信息,咱们需要把线程号转成16进制。例如执行printf "%x\n" 3680 打印输出:e60,那么在jstack中线程号就是0xe60,就是0x 加上 输出结果
  4. 执行 jstack <pid> | grep <tid>:查看详细的线程信息,注意这一步的<pid>表示Java进程id,即可已通过top直接获取,也可以通过jps命令获取,<tid>十六进制的线程idjstack <pid>表示的是使用jstack命令查看pid进程的线程快照,grep <tid>是从所有线程快照的结果中过滤出我们想看的tid线程信息。

通过查看线程信息就可以看出来具体的代码位置,然后进一步分析问题从而处理问题。

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值