一. 调优和故障处理的方向
Java调优通常指的是对Java应用程序进行性能优化和资源管理的过程。调优的方向很多也很广,比如:
- 内存管理:优化Java应用程序的内存使用,包括减少内存泄漏、合理设置堆大小、优化垃圾收集器的选择和参数配置等。
- 线程管理:通过优化线程使用方式,避免过多线程竞争和线程阻塞,提高多线程应用程序的性能和稳定性。
- IO操作:优化文件和网络IO操作,避免阻塞和资源浪费,提高IO操作的效率。
- 数据库访问:优化数据库访问的性能,包括减少数据库连接的创建和关闭、合理利用数据库连接池等。
- 算法和数据结构:优化算法和数据结构的选择和实现,提高程序的执行效率和资源利用率。
- JVM调优:通过调整JVM的参数和配置,优化Java应用程序在JVM上的运行性能,包括堆大小、垃圾收集器、JIT编译器等。
本文主要指的是:内存管理、线程管理、JVM调优 这几个方面。
线上(生产环境)故障指的是在实际运行中出现的影响系统正常功能的问题或错误,这些问题可能导致系统停止响应、性能下降、功能异常、数据丢失等不良后果。人为的代码bug造成的业务bug等,不在本篇中,本篇主要是指比如:OOM异常、机器内存耗尽、机器CPU飙升、访问超时快速定位等等。
写在前面:如果出现问题,最快速的方式就是看日志(前提是能看到),所以建议做好生产环境的日志打印;本篇只是讲一些基本的操作方式,正常环境需要有全面的认知和灵活的应用。
所以建议生产环境启动时加上以下参数,免得出问题了找不到日志:
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/app/appDump.dump //堆转储文件的路径 自定义
-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文件的基本信息统计。
我们常用的功能主要是:Histogram,Dominator Tree,thread_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了怎么办
待补充…