最強のJavaアプリケーションのパフォーマンスチューニングの実践ガイドラインを[ターン]!

:インターネットは良い記事、ターンの非常に包括的なコレクションを表示するには
、Javaアプリケーションのパフォーマンスの最適化が共通のテーマは、そのような遅いページ応答、インターフェースのタイムアウト、高いサーバーの負荷、頻繁に同時データベースの数が少ないなどの典型的なパフォーマンスの問題ですデッドロック。特に、「ラフ激しい速い」インターネットの発展モデル人気の、今日では、肥大化した増加交通システムやコードで、パフォーマンスの問題のすべての種類を注ぐようになりました。

Javaアプリケーションのパフォーマンスのボトルネック・ポイントは、ディスク、メモリ、ネットワークI / Oなど、Javaアプリケーションコード、JVM GC、データベース、キャッシュのような非常に大きい、全身性要因です。図に示すように、アプリケーション層、データベース層、フレーム層、JVM層:私は個人的な経験に基づいて、Javaのパフォーマンスの最適化は、4つのレベルに分割されます。

図1.Java階層モデルのパフォーマンスの最適化
ここに画像を挿入説明
各層のますますハード最適化するため、および問題点を解決するための知識が異なるであろう。例えば、アプリケーションロジック層ように、コードを理解するJavaスレッドスタックによって、問題のコードの行を見つけ、そしてする必要があり、データベース・レベルSQL、位置決めデッドロック分析する必要があり、ソース・コード・フレームワークを理解する層の必要性、メカニズムを理解するためのフレームワークを、GCに必要な作業のJVMレベルおよび種類メカニズムは、様々なパラメータのJVMの向上の役割をよく理解しています。

オンサイト分析とポスト分析:Javaのパフォーマンス・チューニングの周りに、分析の2つの基本的な方法があります。

サイトの予約サイトによって分析して、診断ツールを使用して分析を探します。ライン上のサイトの分析大きな影響、シーンの一部は(特にそれがオンライン・ユーザークリティカルなビジネスに来るとき)は適していません。

その後の分析では、すぐにサービスを回復、マルチサイトデータとして収集する必要があるが、分析後、フィールドデータ収集のために再現します。例と実践の数を共有し、診断ツールのパフォーマンスからスタートしてみましょう。

まず、パフォーマンス診断ツール

一つは、同定されているパフォーマンスの問題とコードシステムの診断のためのパフォーマンス診断され、オンライン性能要求かどうかを判断する前、予めシステムパフォーマンステストの行のペアがあります。

後者は、様々なパフォーマンス測定ツール(例えばJMeterの)で押圧することができ、前者について試験し、この論文では、範囲は、本明細書で説明されていません。

OSレベルとアプリケーションレベル(アプリケーション・コードの診断および診断GCを含む)は、Java:Javaアプリケーションのために、性能診断ツールは、主に二つの層に分けられます。

OS診断
の診断OSの主な関心事は、三つの側面にはCPU、メモリ、I / Oです。

二、CPU診断

CPU平均負荷(負荷の平均)、CPU使用率、コンテキスト切り替え(コンテキスト・スイッチ)のための主要な関心事。

トップコマンドによって、システムの負荷と平均CPU使用率を表示することができる。2コマンド・ステータスを介してシステムの上面図です。

図の2.topコマンドの例。
ここに画像を挿入説明

3つの数値の平均荷重:63.66,58.39,57.18、それぞれ、1分以上、5分、機械負荷の15分。経験によると、値はCPUの0.7未満*数であれば、システムが正常に動作している。以上、この値ならば、でも、4,5回CPUコアの数まで、システムの負荷がかなり高いです。

図2に負荷が57.18,1負荷は(16コアシステムの場合)63.66分で15分に達している、システムは、ローディング問題は、具体的な理由を検索する必要が生じ、そして本傾向がさらに大きくなる説明。

図3に示したコマンド・コンテキスト・スイッチ、vmstatのCPUによって観察することができます。

図3.vmstatコマンドの例
ここに画像を挿入説明

コンテキストスイッチの発生のシーン数は、次の主要なカテゴリがあります。

1)タイムスライスは、CPU、通常のタスクのスケジューリングを満了します。

2)他のより高い優先度のタスクプリエンプションあります。

3)現在のタスク、次のタスクスイッチを一時停止し、I / O障害が発生したタスクを実行します。

4)ユーザコードイニシアチブは、CPUを放棄する現在のタスクを一時停止します。

あるので、5)プリエンプティブマルチタスクのリソースは、全くグラブが中断されていません。

6)ハードウェア割り込み。

共有リソースの競合から主に切り替えるJavaスレッドコンテキスト。ロックの粒度が大きすぎない限り、一般的に、単一のオブジェクトのロックシステムはほとんど、ボトルネックとなっていません。しかし、アクセス頻度が高い場合、コードのロックされたブロック内の連続する複数のオブジェクトは、コンテキストスイッチの多数現れるシステムのボトルネックになることができます。

例えば、一度大きな同時ログ、頻繁なコンテキスト切り替えにおける我々のシステムのlog4j 1.1体積印刷において、スレッドブロックの多くは、関連するコードは、リスト1に示されているシステムスループットの大きな低下につながる、アップグレードしますlog4jは2.xのだけ、この問題を解決します。

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);
 }
 …
 }
}

三、メモリ

オペレーティングシステムの観点から、メモリ、アプリケーションプロセスの妥当性への注意、あなたはメモリ使用量を表示する無料-mコマンドを使用することができます。

もし式VIRT = SWAP +用いるスワップパーティション特定のアプリケーション(SWAP)、swappinessをできるJavaアプリケーションのパフォーマンスにスワップあまり効果の使用を計算することができるRESによると、トップコマンド仮想メモリVIRTと物理メモリRESによって使用されるプロセスを表示することができできるだけ小さい値に移しました。

Javaアプリケーションのために、すべての後に、メモリよりもはるかに遅いパフォーマンス、ディスクパフォ​​ーマンスに影響を与えることができますあまりにも多くのスワップを取るので。

四、 I/O

I / Oは、ディスクI / O、および一般的なI / OのボトルネックになりやすいネットワークI / O、ディスクを含んでいます。I / Oが正常で見ることができます/ディスクI O待つCPUを通じて、ディスクは、iostatので読み取る方法を確認することができます。

ディスクI / Oが遅い、またはディスクの障害を示す、高い状態にあった場合、それがパフォーマンスのボトルネック、アプリケーションの最適化やディスク交換の必要性となっています。

通常のトップ、psのに加え、vmstatのは、コマンドのiostat、そのようように、Solarisでmpstat、tcpdumpを、netstatコマンド、pidstat、SARとなどのシステムの問題を診断するために、他のLinuxツールがあります。参考のため、図4に示すように、Linuxのブレンダン要約は、デバイス性能診断ツールの種類を示しています。

図パフォーマンスの観測ツール4.Linux
ここに画像を挿入説明

五、Javaアプリケーションおよび診断ツール

アプリケーションコードは、パフォーマンス上の問題のクラスのパフォーマンスの問題を解決することは比較的容易です。いくつかのアプリケーションレベルの監視警報を通じコードによって決定問題とコードがある場合は、直接配置することができ、またはトップ+ jstackを経由して、コードをスレッド化問題に配置問題のスレッドスタックを、特定し、問題を見つけることができます。より複雑なため、論理以上のコードセグメント、ほとんどの場合、アプリケーション・コードは、パフォーマンスログを印刷してストップウォッチ、パフォーマンスの問題を配置することができます。

診断スレッドスタック、GCおよび他の態様を含む一般的なJavaアプリケーションの診断。

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 线程
ここに画像を挿入説明

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

图 6.jstack 查看线程堆栈
ここに画像を挿入説明

JProfiler

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

图 7. 通过 JProfiler 进行内存分析
ここに画像を挿入説明
六、 GC 诊断

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

jstat

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

jstat –gcxxx -t pid ,如图 8 所示。

图 8.jstat 命令示例
ここに画像を挿入説明

jmap

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

MAT

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

图 9.MAT 示例
ここに画像を挿入説明

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

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

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

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

图 10. 常用 GC 参数
ここに画像を挿入説明

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

七、性能优化实践

在介绍了一些常用的性能诊断工具后,下面将结合我们在 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 监控统计
ここに画像を挿入説明

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

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

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

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

图 12. 通过 MAT 分析堆栈现场
ここに画像を挿入説明

堆栈现场如图 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 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. 死锁语句
ここに画像を挿入説明

其中,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 等。

八、总结与建议

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

1)基础性能的调优

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

2)数据库性能优化

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

3)应用架构优化

時間のための典型的な練習空間を使用して、事前に予め計算等を含む、または分散戦略の導入、計算及び記憶のレベル、等を計算する元のクラスタのパフォーマンスのボトルネックを解決するために、新しい機能と、いくつかの新しいコンピューティングやストレージフレームワークを導入します等、システムの負荷をある程度低減することができます。

最適化の4)動作レベル

テクノロジーは、実際には、回避やビジネスで調整することができた場合、実際には、あなたが最も頻繁に、理由発生する特殊なビジネスシナリオの大部分を見ることができ、パフォーマンスの問題の多くのシーンでは、システムの性能を向上させる唯一の手段ではありません効果的。

转】【https://mp.weixin.qq.com/s/wK7Yb_f_AY9miElZGEBQJQ

おすすめ

転載: blog.csdn.net/weixin_37586375/article/details/94767341