Java application performance tuning of actual practice

Java application performance optimization is a common theme, typical performance issues such as slow page response, the interface timeout, high server load, low number of concurrent database frequent deadlocks. Especially in the "rough fierce fast" Internet development model popular today, with bloated increasing traffic systems and codes, all kinds of performance problems began to pour.

Java application performance bottleneck point is very large, systemic factors such as disk, memory, network I / O, etc., Java application code, JVM GC, databases, caching. I based on personal experience, the  Java performance optimization is divided into four levels: the application layer, a database layer, a frame layer, the JVM layer , as shown in FIG.

FIG hierarchical model performance optimization 1.Java

Java application performance tuning of actual practice

 

 

Each layer optimization increasingly hard, and knowledge to solve the problems involved will be different. For example, the application logic layer needs to understand the code, locate the line of code in question by the Java thread stack, and so on; the database level need to analyze the SQL, positioning deadlock; layer need to understand the source code framework, a framework for understanding the mechanism; JVM level and type of work required for the GC mechanisms have a good understanding of the role of various parameters JVM gains.

Around Java performance tuning, there are two basic methods of analysis: on-site analysis and post analysis.

Site analysis by reservation site, and then locate the analysis using the diagnostic tool. Site analysis greater impact on the line, part of the scene (particularly when it comes to online users critical business) is not suitable.

Later analysis needs to collect as multi-site data, then immediately restore service, but after the analysis and reproduced for field data collection. Let's start from the performance of diagnostic tools, share a number of cases and practice.

1 Performance Diagnostic Tool

 

One is performance diagnostics for the diagnosis of performance problems have been identified and code systems, there is a pair of lines on the pre-advance system performance testing, to determine whether the on-line performance requirements.

This paper tested for the former, the latter can be pressed with various performance measurement tool (e.g. the JMeter), the range is not discussed herein.

For Java applications, performance diagnostic tools are mainly divided into two layers: OS level and application-level Java (including application code diagnostics and diagnostic GC).

OS diagnosis

OS diagnosis of major concern is the CPU, Memory, I / O in three aspects.

2 CPU diagnosis

 

The main concern for the CPU average load (Load Average), CPU usage, context switches (Context Switch).

Can view the system load and average CPU usage by the top command, FIG. 2 is a top view of a system through a command status.

2.top command example of FIG.

Java application performance tuning of actual practice

 

 

 

Average load of three numbers: 63.66,58.39,57.18, respectively, over 1 minute, 5 minutes, 15 minutes of machine load. According to experience, if the value is less than 0.7 * number of CPU, the system is working properly; if more than this value, even up to four or five times the number of CPU cores, the load on the system is significantly higher.

In Figure 2 the load has reached 15 minutes 57.18,1 load is 63.66 minutes (for the 16-core system), the system described loading problems arise, and the present tendency is further increased, the need to locate specific reasons.

Can be viewed by the CPU vmstat command context switches, shown in Figure 3:

FIG 3.vmstat command examples

Java application performance tuning of actual practice

 

 

 

Scene number of occurrences of context switches have the following main categories:

  • 1) time slice expires, CPU normal task scheduling;
  • 2) the other being a higher priority task preemption;
  • 3) perform tasks encountered I / O obstruction, suspend the current task, a task switch to the next;
  • 4) user code initiative suspend the current task to give up CPU;
  • 5) preemptive multitasking resources, because there is no grab is suspended;
  • 6) hardware interrupt.

 

Java thread context switching mainly from the shared resource competition. Generally a single object locking system rarely become a bottleneck, unless the lock granularity is too large. However, an access frequency is high, a plurality of successive objects in a locked block of code may appear a large number of context switches, become a system bottleneck.

For example, once in our system log4j 1.x volume printing at larger concurrent logs, frequent context switching, a lot of thread blocking, lead to a large drop in system throughput, which is the relevant code is shown in Listing 1, to upgrade log4j 2.x only solve this problem.

for(Category c = this; c != null; c=c.parent) {
 // Protected against simultaneous call to addAppender, removeAppender,…
 synchronized(c) {
 if (c.aai != null) {
 write += c.aai.appendLoopAppenders(event);
 }
 …
 }
}

 

3 Memory

 

From the perspective of the operating system, memory, attention to the adequacy of the application process, you can use the free -m command to view memory usage.

You can view the process used by the top command virtual memory VIRT and physical memory RES, according to the formula VIRT = SWAP + RES can calculate the swap partition specific application used (Swap), the use of swap too much effect on Java application performance can be swappiness transferred to the value as small as possible.

Because for Java applications, take up too much swap can affect performance, disk performance, after all, much slower than memory.

4 I / O

 

I / O includes a disk I / O, and network I / O, disk more prone to I / O bottlenecks in general. Can see how disk read by iostat, through the CPU I O wait can be seen / disk I / O is normal.

If the disk I / O has been in a high state, indicating slow or disk failure, it has become a performance bottleneck, the need for application optimization or disk replacement.

Addition to the usual top, ps, vmstat, iostat commands, there are other Linux tools to diagnose system problems, such as mpstat, tcpdump, netstat, pidstat, sar and so on. Linux Brendan summary lists different types of device performance diagnostic tools, as shown in FIG. 4, for reference.

FIG Performance Observation tool 4.Linux

 

Java application performance tuning of actual practice

 

 

5 Java 应用诊断及工具

 

应用代码性能问题是相对好解决的一类性能问题。通过一些应用层面监控报警,如果确定有问题的功能和代码,直接通过代码就可以定位;或者通过 top+jstack,找出有问题的线程栈,定位到问题线程的代码上,也可以发现问题。对于更复杂,逻辑更多的代码段,通过 Stopwatch 打印性能日志往往也可以定位大多数应用代码性能问题。

常用的 Java 应用诊断包括线程、堆栈、GC 等方面的诊断。

jstack

jstack 命令通常配合 top 使用,通过 top -H -p pid 定位 Java 进程和线程,再利用 jstack -l pid 导出线程栈。由于线程栈是瞬态的,因此需要多次 dump,一般 3 次 dump,一般每次隔 5s 就行。将 top 定位的 Java 线程 pid 转成 16 进制,得到 Java 线程栈中的 nid,可以找到对应的问题线程栈。

图 5. 通过 top –H -p 查看运行时间较长 Java 线程

Java application performance tuning of actual practice

 

 

如图 5 所示,其中的线程 24985 运行时间较长,可能存在问题,转成 16 进制后,通过 Java 线程栈找到对应线程 0x6199 的栈如下,从而定位问题点,如图 6 所示。

图 6.jstack 查看线程堆栈

Java application performance tuning of actual practice

 

 

 

JProfiler

JProfiler 可对 CPU、堆、内存进行分析,功能强大,如图 7 所示。同时结合压测工具,可以对代码耗时采样统计。

图 7. 通过 JProfiler 进行内存分析

Java application performance tuning of actual practice

 

 

6 GC 诊断

 

Java GC 解决了程序员管理内存的风险,但 GC 引起的应用暂停成了另一个需要解决的问题。JDK 提供了一系列工具来定位 GC 问题,比较常用的有 jstat、jmap,还有第三方工具 MAT 等。

jstat

jstat 命令可打印 GC 详细信息,Young GC 和 Full GC 次数,堆信息等。其命令格式为

jstat –gcxxx -t pid <interval> <count>,如图 8 所示。

图 8.jstat 命令示例

Java application performance tuning of actual practice

 

 

 

jmap

jmap 打印 Java 进程堆信息 jmap –heap pid。通过 jmap –dump:file=xxx pid 可 dump 堆到文件,然后通过其它工具进一步分析其堆使用情况

MAT

MAT 是 Java 堆的分析利器,提供了直观的诊断报告,内置的 OQL 允许对堆进行类 SQL 查询,功能强大,outgoing reference 和 incoming reference 可以对对象引用追根溯源。

图 9.MAT 示例

Java application performance tuning of actual practice

 

 

图 9 是 MAT 使用示例,MAT 有两列显示对象大小,分别是 Shallow size 和 Retained size,前者表示对象本身占用内存的大小,不包含其引用的对象,后者是对象自己及其直接或间接引用的对象的 Shallow size 之和,即该对象被回收后 GC 释放的内存大小,一般说来关注后者大小即可。

对于有些大堆 (几十 G) 的 Java 应用,需要较大内存才能打开 MAT。

通常本地开发机内存过小,是无法打开的,建议在线下服务器端安装图形环境和 MAT,远程打开查看。或者执行 mat 命令生成堆索引,拷贝索引到本地,不过这种方式看到的堆信息有限。

为了诊断 GC 问题,建议在 JVM 参数中加上-XX:+PrintGCDateStamps。常用的 GC 参数如图 10 所示。

图 10. 常用 GC 参数

Java application performance tuning of actual practice

 

 

对于 Java 应用,通过 top+jstack+jmap+MAT 可以定位大多数应用和内存问题,可谓必备工具。有些时候,Java 应用诊断需要参考 OS 相关信息,可使用一些更全面的诊断工具,比如 Zabbix(整合了 OS 和 JVM 监控)等。在分布式环境中,分布式跟踪系统等基础设施也对应用性能诊断提供了有力支持。

7 性能优化实践

 

在介绍了一些常用的性能诊断工具后,下面将结合我们在 Java 应用调优中的一些实践,从 JVM 层、应用代码层以及数据库层进行案例分享。

JVM 调优:GC 之痛

XX商业平台某系统重构时选择 RMI 作为内部远程调用协议,系统上线后开始出现周期性的服务停止响应,暂停时间由数秒到数十秒不等。通过观察 GC 日志,发现服务自启动后每小时会出现一次 Full GC。由于系统堆设置较大,Full GC 一次暂停应用时间会较长,这对线上实时服务影响较大。

经过分析,在重构前系统没有出现定期 Full GC 的情况,因此怀疑是 RMI 框架层面的问题。通过公开资料,发现 RMI 的 GDC(Distributed Garbage Collection,分布式垃圾收集)会启动守护线程定期执行 Full GC 来回收远程对象,清单 2 中展示了其守护线程代码。

清单 2.DGC 守护线程源代码

private static class Daemon extends Thread {
 public void run() {
 for (;;) { 
 //…
 long d = maxObjectInspectionAge();
 if (d >= l) {
 System.gc(); 
 d = 0;
 }
 //…
 }
 }
}

定位问题后解决起来就比较容易了。一种是通过增加-XX:+DisableExplicitGC 参数,直接禁用系统 GC 的显示调用,但对使用 NIO 的系统,会有堆外内存溢出的风险。

另一种方式是通过调大 -Dsun.rmi.dgc.server.gcInterval 和-Dsun.rmi.dgc.client.gcInterval 参数,增加 Full GC 间隔,同时增加参数-XX:+ExplicitGCInvokesConcurrent,将一次完全 Stop-The-World 的 Full GC 调整为一次并发 GC 周期,减少应用暂停时间,同时对 NIO 应用也不会造成影响。

从图 11 可知,调整之后的 Full GC 次数 在 3 月之后明显减少。

图 11.Full GC 监控统计

Java application performance tuning of actual practice

 

 

GC 调优对高并发大数据量交互的应用还是很有必要的,尤其是默认 JVM 参数通常不满足业务需求,需要进行专门调优。GC 日志的解读有很多公开的资料,本文不再赘述。

GC 调优目标基本有三个思路:降低 GC 频率,可以通过增大堆空间,减少不必要对象生成;降低 GC 暂停时间,可以通过减少堆空间,使用 CMS GC 算法实现;避免 Full GC,调整 CMS 触发比例,避免 Promotion Failure 和 Concurrent mode failure(老年代分配更多空间,增加 GC 线程数加快回收速度),减少大对象生成等。

应用层调优:嗅到代码的坏味道

从应用层代码调优入手,剖析代码效率下降的根源,无疑是提高 Java 应用性能的很好的手段之一。

某商业广告系统(采用 Nginx 进行负载均衡)某次日常上线后,其中有几台机器负载急剧升高,CPU 使用率迅速打满。我们对线上进行了紧急回滚,并通过 jmap 和 jstack 对其中某台服务器的现场进行保存。

图 12. 通过 MAT 分析堆栈现场

Java application performance tuning of actual practice

 

 

 

堆栈现场如图 12 所示,根据 MAT 对 dump 数据的分析,发现最多的内存对象为 byte[] 和 java.util.HashMap $Entry,且 java.util.HashMap $Entry 对象存在循环引用。初步定位在该 HashMap 的 put 过程中有可能出现了死循环问题(图中 java.util.HashMap $Entry 0x2add6d992cb8 和 0x2add6d992ce8 的 next 引用形成循环)。

查阅相关文档定位这属于典型的并发使用的场景错误 (http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6423457) ,简要的说就是 HashMap 本身并不具备多线程并发的特性,在多个线程同时 put 操作的情况下,内部数组进行扩容时会导致 HashMap 的内部链表形成环形结构,从而出现死循环。

针对此次上线,最大的改动在于通过内存缓存网站数据来提升系统性能,同时使用了懒加载机制,如清单 3 所示。

清单 3. 网站数据懒加载代码

private static Map<Long, UnionDomain> domainMap = new HashMap<Long, UnionDomain>();
 private boolean isResetDomains() {
 if (CollectionUtils.isEmpty(domainMap)) {
 // 从远端 http 接口获取网站详情
 List<UnionDomain> newDomains = unionDomainHttpClient
 .queryAllUnionDomain();
 if (CollectionUtils.isEmpty(domainMap)) {
 domainMap = new HashMap<Long, UnionDomain>();
 for (UnionDomain domain : newDomains) {
 if (domain != null) {
 domainMap.put(domain.getSubdomainId(), domain);
 }
 }
 }
 return true;
 }
 return false;
 }

可以看到此处的 domainMap 为静态共享资源,它是 HashMap 类型,在多线程情况下会导致其内部链表形成环形结构,出现死循环。

通过对前端 Nginx 的连接和访问日志可以看到,由于在系统重启后 Nginx 积攒了大量的用户请求,在 Resin 容器启动,大量用户请求涌入应用系统,多个用户同时进行网站数据的请求和初始化工作,导致 HashMap 出现并发问题。在定位故障原因后解决方法则比较简单,主要的解决方法有:

  • (1)采用 ConcurrentHashMap 或者同步块的方式解决上述并发问题;
  • (2)在系统启动前完成网站缓存加载,去除懒加载等;
  • (3)采用分布式缓存替换本地缓存等。

 

对于坏代码的定位,除了常规意义上的代码审查外,借助诸如 MAT 之类的工具也可以在一定程度对系统性能瓶颈点进行快速定位。但是一些与特定场景绑定或者业务数据绑定的情况,却需要辅助代码走查、性能检测工具、数据模拟甚至线上引流等方式才能最终确认性能问题的出处。以下是我们总结的一些坏代码可能的一些特征,供大家参考:

  • (1)代码可读性差,无基本编程规范;
  • (2)对象生成过多或生成大对象,内存泄露等;
  • (3)IO 流操作过多,或者忘记关闭;
  • (4)数据库操作过多,事务过长;
  • (5)同步使用的场景错误;
  • (6)循环迭代耗时操作等。

 

数据库层调优:死锁噩梦

对于大部分 Java 应用来说,与数据库进行交互的场景非常普遍,尤其是 OLTP 这种对于数据一致性要求较高的应用,数据库的性能会直接影响到整个应用的性能。搜狗商业平台系统作为广告主的广告发布和投放平台,对其物料的实时性和一致性都有极高的要求,我们在关系型数据库优化方面也积累了一定的经验。

对于广告物料库来说,较高的操作频繁度(特别是通过批量物料工具操作)很极易造成数据库的死锁情况发生,其中一个比较典型的场景是广告物料调价。客户往往会频繁的对物料的出价进行调整,从而间接给数据库系统造成较大的负载压力,也加剧了死锁发生的可能性。下面以搜狗商业平台某广告系统广告物料调价的案例进行说明。

某商业广告系统某天访问量突增,造成系统负载升高以及数据库频繁死锁,死锁语句如图 13 所示。

图 13. 死锁语句

Java application performance tuning of actual practice

 

 

其中,groupdomain 表上索引为 idx_groupdomain_accountid (accountid),idx_groupdomain_groupid(groupid),primary(groupdomainid) 三个单索引结构,采用 Mysql innodb 引擎。

此场景发生在更新组出价时,场景中存在着组、组行业(groupindus 表)和组网站(groupdomain 表)。

当更新组出价时,若组行业出价使用组出价(通过 isusegroupprice 标示,若为 1 则使用组出价)。同时若组网站出价使用组行业出价(通过 isuseindusprice 标示,若为 1 则使用组行业出价)时,也需要同时更新其组网站出价。由于每个组下面最大可以有 3000 个网站,因此在更新组出价时会长时间的对相关记录进行锁定。

从上面发生死锁的问题可以看到,事务 1 和事务 2 均选择了 idx_groupdomain_accountid 的单列索引。根据 Mysql innodb 引擎加锁的特点,在一次事务中只会选择一个索引使用,而且如果一旦使用二级索引进行加锁后,会尝试将主键索引进行加锁。进一步分析可知事务 1 在请求事务 2 持有的`idx_groupdomain_accountid`二级索引加锁(加锁范围“space id 5726 page no 8658 n bits 824 index”),但是事务 2 已获得该二级索引 (“space id 5726 page no 8658 n bits 824 index”) 上所加的锁,在等待请求锁定主键索引 PRIMARY 索引上的锁。由于事务 2 等待执行时间过长或长时间不释放锁,导致事务 1 最终发生回滚。

通过对当天访问日志跟踪可以看到,当天有客户通过脚本方式发起大量的修改推广组出价的操作,导致有大量事务在循环等待前一个事务释放锁定的主键 PRIMARY 索引。该问题的根源实际上在于 Mysql innodb 引擎对于索引利用有限,在 Oracle 数据库中此问题并不突出。

解决的方式自然是希望单个事务锁定的记录数越少越好,这样产生死锁的概率也会大大降低。最终使用了(accountid, groupid)的复合索引,缩小了单个事务锁定的记录条数,也实现了不同计划下的推广组数据记录的隔离,从而减少该类死锁的发生几率。

通常来说,对于数据库层的调优我们基本上会从以下几个方面出发:

(1)在 SQL 语句层面进行优化:慢 SQL 分析、索引分析和调优、事务拆分等;

(2)在数据库配置层面进行优化:比如字段设计、调整缓存大小、磁盘 I/O 等数据库参数优化、数据碎片整理等;

(3)从数据库结构层面进行优化:考虑数据库的垂直拆分和水平拆分等;

(4)选择合适的数据库引擎或者类型适应不同场景,比如考虑引入 NoSQL 等。

8 总结与建议

 

性能调优同样遵循 2-8 原则,80%的性能问题是由 20%的代码产生的,因此优化关键代码事半功倍。同时,对性能的优化要做到按需优化,过度优化可能引入更多问题。对于 Java 性能优化,不仅要理解系统架构、应用代码,同样需要关注 JVM 层甚至操作系统底层。总结起来主要可以从以下几点进行考虑:

1)基础性能的调优

这里的基础性能指的是硬件层级或者操作系统层级的升级优化,比如网络调优,操作系统版本升级,硬件设备优化等。比如 F5 的使用和 SDD 硬盘的引入,包括新版本 Linux 在 NIO 方面的升级,都可以极大的促进应用的性能提升;

2)数据库性能优化

包括常见的事务拆分,索引调优,SQL 优化,NoSQL 引入等,比如在事务拆分时引入异步化处理,最终达到一致性等做法的引入,包括在针对具体场景引入的各类 NoSQL 数据库,都可以大大缓解传统数据库在高并发下的不足;

3)应用架构优化

Introduce some new computing or storage framework, with the new feature to solve the performance bottleneck of the original cluster computing the like; or the introduction of a distributed strategy, level of computing and storage, comprising pre-calculated in advance and the like, using a typical practice space for time and the like; the system load can be reduced to some extent;

4) the operational level of optimization

Technology is not the only means to enhance system performance in many scenes of the performance problems, in fact, you can see a large part because of the special business scenarios arising, if able to evade or adjustment in business, in fact, often the most Effective.

Guess you like

Origin blog.csdn.net/weixin_45132238/article/details/94005244