Java应用性能分析与调优实践

性能问题表象就是应用系统运行慢,影响客户体验。要解决性能问题也无非就是找到原因然后根据原因对症下药。本文从这两方面概述Java应用性能分析与调优方法论。

一、性能分析的思路

性能分析主要是监测代码中各个方法的运行耗时,常见有两种思路:插桩和抽样。
1、插桩分析器
插桩就是人为或者切面代理自动在方法的开头和结尾获取系统时间,然后打印一下时间差,类似下面这样:

void methodA() {
      long start = System.currentTimeMillis();
      //// 做工作
      long duration = System.currentTimeMillis() - start;
      System.out.println(“methodA took “ + duration + “ms”);
}

优点:使用纯java代码,适用所有类型JVM;

缺点:插入的方法调用会导致显著的性能损失并严重影响结果,现在一般不用了;

2、抽样分析器
抽样分析器在被分析程序执行时进行抽样。这类分析器会定期向JVM请求当前运行程序的堆栈,通常是每10毫秒到20毫秒一次。然后,分析器会使用这些信息来估算性能。抽样分析器每次迭代都会获取当前的(Java)线程列表。然后,它会随机选择一个线程子集进行抽样。通常,这个子集的大小在 5 到 8 之间,因为每次迭代对太多线程进行抽样会增加运行分析器的性能影响。在分析具有大量线程的应用程序时,请注意这一点。然后,分析器向每个选定的线程发送一个信号,这将导致它们停下来调用信号处理程序。此信号处理程序会获取并存储其线程的堆栈跟踪。在每次迭代结束时,分析器会收集所有堆栈跟踪信息并进行后处理。

优点:不会修改程序,开销比较小,不会对结果产生明显的影响。

缺点:运行时间比较短的方法可能不会在性能分析概要中出现。

二、几款开源的分析器
目前最著名的开源分析器有3个:VisualVM、async-profiler 和 JDK Flight Recorder(JFR)。这些分析器都处于积极开发发展过程中,可用于各种应用程序。它们都是抽样分析器。VisualVM 是唯一支持插桩分析的分析器。

1、“外部”分析器

外部分析器不是直接实现到JVM中,而是使用API来收集特定线程的堆栈跟踪信息。对于只使用API的分析器,同一个版本可以用于不同的JVM版本和供应商(如 OpenJDK 和 OpenJ9)。最著名的外部分析器有两个:VisualVM和async-profiler;它们的主要区别在于它们使用的 API。VisualVM 使用官方的Java管理扩展(JMX)来获取线程的堆栈跟踪信息。async-profiler使用非官方的 AsyncGetCallTrace API。两者各有优缺点,但通常,JMX及相关API被认为更安全,而 AsyncGetCallTrace 更精确

(1)VisualVM 使用Sampler抽样器

(2)Async-profiler

Async-profiler可以通过许多嵌入了async-profiler的工具(IntelliJ Ultimate Profiler 和 AppIication Performance Monitors)使用它,或直接将其作为本机 Java 代理来使用。从项目的GitHub页面下载 async-profiler,它包含特定于平台的二进制文件,不支持Windows。因此,创建了app-loader项目,将所有async-profiler二进制文件封装到一个多平台二进制文件中,使得嵌入并使用这个分析器变得更容易。

#假设你下载了特定于平台的 libasyncProfiler.so,则只需在调用Java二进制文件时添加以下选项,即可分析 Java 应用程序的性能,这个调用告诉 async-profiler 生成一个火焰图。这是一种非常流行的可视化方式。
java -agentpath:libasyncProfiler.so=start,event=cpu,file=flame.html,flamegraph …

#也可以用它创建JFR文件:
java -agentpath:libasyncProfiler.so=start,event=cpu,file=profile.jfr,jfr …

另外,Arthas 也使用 async-profiler 生成 CPU/内存火焰图进行性能分析,可以作在线分析性能。

参考:

Java 性能分析工具 Async-profiler

2、“内置”分析器
OpenJDK和GraalVM仅有一个内置分析器Java Flight Recorder(JFR);它的工作原理与 async-profiler大致相同,主要区别是它直接使用内部的 JVM API同样精确,但更稳定。JRockit 最初开发运行时分析器是为了内部使用,但它也越来越受应用程序开发人员的欢迎。后来,在 Oracle 收购了其开发公司之后,这些特性被集成到了 Oracle JDK 中。最终,Oracle 将该工具与 JDK11 一起开源,从那时起,它就成了 OpenJDK JVM 的内置分析工具,不再支持 OpenJ9 等其他 JVM 了。与 async-profiler 相比,JFR 的主要优势是它存在于所有平台的 OpenJDK 中,甚至在 Windows 上。此外,JFR 更稳定一些,记录的事件和信息也更多。JFR 有一个名为 JDK Mission Control(JMC)的 GUI,它让你可以分析 JVM 性能并查看生成的 JFR 性能分析概要。

#该分析器的使用很简单,可以通过在 Java 二进制文件的调用中添加以下选项
$ java \
  -XX:+UnlockDiagnosticVMOptions \
  -XX:+DebugNonSafepoints \  # improves precision
  -XX:+FlightRecorder \
  -XX:StartFlightRecording=filename=file.jfr \
  arguments

#或者使用 JDK 命令行工具jcmd 启动和禁用它:
$ jcmd PID JFR.start
$ jcmd PID JFR.dump filename=file.jfr
$ jcmd PID JFR.stop

三、闭源性能分析器

YourKit、JProfiler。

--------------------------------------------------------------------------------------------------------

四、定位性能差的原因

1、借助工具:YourKit (类似的还有JProfiler,个人倾向YourKit),YourKit 是收费的,但是对于聪明的你来说不是问题,你懂的。YourKit 帮助文档非常详细,而且还有视频指导。

2、应用场景:

(1)应用系统在服务器端(比如Linux系统),我们需要在服务器上安装对应操作系统的YourKit ,然后使用YourKit 启动应用系统,然后去系统界面操作相关业务,YourKit 会录下一个日志,然后把这个日志文件给开发人员进行分析,这个一般是产品已经上线或试运行阶段;

安装YourKit后,使用integrate.sh经过若干步骤生成集成参数,类似下面:

  

-agentpath:/usr/local/YourKit-JavaProfiler-2020.9/bin/linux-x86-64/libyjpagent.so=disablestacktelemetry,delay=10000,port=12345,listen=localhost

添加到java启动参数后面启动即可:

java -Xms768m -Xmx4096m -Djava.net.preferIPv4Stack=true -Dloader.path=./target/lib -jar ./target/test.jar -agentpath:/usr/local/YourKit-JavaProfiler-2020.9/bin/linux-x86-64/libyjpagent.so=disablestacktelemetry,delay=10000,port=12345,listen=localhost

  

(2)使用IDE本地运行应用直接分析,这个用于开发阶段

 

 

 

 

 注意:使用Profile启动且使用Trace模式分析,不要使用Attach附加方式分析,因为Attach方式下YourKit 有些功能不能使用 导致分析数据不全。

五、调优

总体策略:空间 换 时间。

发生性能问题的原因很多种

比如:

(1)SQL查询慢,进行SQL优化,比如增加索引等;

(2)常用数据反复查询:引入缓存,将常用数据放入缓存;

(3)复杂业务处理:12模式并发编程

(4)高并发:增加应用服务器、数据库服务器(分库分表、读写分离等)

 我们这里不讨论(1)(2)(4),这三种网上文章很多,而且(1)(2)比较简单,(4)一版架构师都做好了,业务开发人员无需关注。我们着重讨论(3),基本上所有开发人员都会涉及。

六、多线程设计模式

为了提高代码执行效率而引入并发编程,但是并发编程会带来数据安全、死锁等问题,需要一套方法论来规范使用并发编程。
(1)Immutable Object不可变对象模式:在不引入锁的条件下,能保证访问共享变量时是线程安全的,缺点是会频繁的创建变量。(案例2-4)
(2)Guarded Suspension保护性暂挂模式:将线程间的等待与唤醒规范化(案例5-7)
(3)Two-phase Termination两阶段终止模式:优雅的终止run方法的执行,让其有做一些收尾工作的可能。(案例8-11)
(4)Promise承诺模式:能优雅的让当前线程获取到另一个线程的执行结果。(案例12-14)
(5)生产者/消费者模式:让输入逻辑和输出逻辑解耦,让两端易于维护。(案例15-18)
(6)Active Object主动对象模式:为了保证系统的吞吐量,让其真正的执行请求异步化,请求将成为一个任务对象,放入缓冲区,让其他线程来处理。(案例19-21)
(7)Thread Pool线程池模式:避免线程资源的频繁创建与销毁导致的性能损耗,同时也是对线程的开辟数量进行管控。(案例22-25)
(8)Thread Specific Storage线程特有存储模式:让每个线程都拥有一份相同全局变量的副本,避免了线程安全问题,只不过可能导致内存泄漏。(案例26-28)
(9)Serial Thread Confinement串行线程封闭模式:将并发任务串行化,交由统一的线程去处理,保证线程安全。(案例29-30)
(10)Master-Slave主仆模式*:将一个任务切分成多个子任务,并且由一个主任务对其他子任务负责管控,分而治之确保执行效率。(案例31-33)
(11)Pipeline流水线模式*:让有依赖关系的子任务进行任务编排,能够顺利的并行执行。(案例34-37)
(12)半同步/半异步模式*:将原有任务按执行效率高低,进行子任务的拆分,从而保证系统的整体的执行效率。(案例38-40)
多线程设计模式真正要解决的问题核心就两个:确保任务的执行效率与确保数据的线程安全。另外,一种设计模式想要真正落地,很大程度上必须同时杂糅多种其他设计模式的思想。通过学习吸取到的是设计模式解决问题的思想,不要纠结于一种模式应该以什么样的形式呈现,利用模式传递的思想,解决问题才是最重要的,并发编程是一件比较复杂的事情,通过本课程的学习,最多也只能让你们了解到并发编程一些内功心法。

 

参考:

YourKit官网

 Linux版本的youkit安装

yourkit性能监控工具,远程监控

posted @ 2022-06-08 17:57  cac2020  阅读(704)  评论(0编辑  收藏  举报