怎么对 Java 程序调优?不看别后悔!

3、Memory

从操作系统角度,内存关注应用进程是否足够,可以使用 free –m 命令查看内存的使用情况。

通过 top 命令可以查看进程使用的虚拟内存 VIRT 和物理内存 RES,根据公式 VIRT = SWAP + RES 可以推算出具体应用使用的交换分区(Swap)情况,使用交换分区过大会影响 Java 应用性能,可以将 swappiness 值调到尽可能小。

因为对于 Java 应用来说,占用太多交换分区可能会影响性能,毕竟磁盘性能比内存慢太多。

4、I/O

I/O 包括磁盘 I/O 和网络 I/O,一般情况下磁盘更容易出现 I/O 瓶颈。通过 iostat 可以查看磁盘的读写情况,通过 CPU 的 I/O wait 可以看出磁盘 I/O 是否正常。

如果磁盘 I/O 一直处于很高的状态,说明磁盘太慢或故障,成为了性能瓶颈,需要进行应用优化或者磁盘更换。

除了常用的 top、 ps、vmstat、iostat 等命令,还有其他 Linux 工具可以诊断系统问题,如 mpstat、tcpdump、netstat、pidstat、sar 等。Brendan 总结列出了 Linux 不同设备类型的性能诊断工具,如图 4 所示,可供参考。

图 4.Linux 性能观测工具

5、Java 应用诊断及工具

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

常用的 Java 应用诊断包括线程、堆栈、GC 等方面的诊断。之前分享了一份JVM 46页的干货资料,关注微信公众号:Java技术栈,在后台回复:jvm46,即可领取。见这里:46张PPT弄懂JVM、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 线程

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

图 6.jstack 查看线程堆栈

JProfiler

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

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

6、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 监控)等。

在分布式环境中,分布式跟踪系统等基础设施也对应用性能诊断提供了有力支持。

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 监控统计

GC 调优对高并发大数据量交互的应用还是很有必要的,尤其是默认 JVM 参数通常不满足业务需求,需要进行专门调优。GC 日志的解读有很多公开的资料,本文不再赘述。关注微信公众号:Java技术栈,在后台回复:jvm,可以获取我整理的 N 篇 jvm 教程,都是干货。

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

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

从应用层代码调优入手,剖析代码效率下降的根源,无疑是提高 Java 应用性能的很好的手段之一。推荐阅读:Java性能优化的50个细节!

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

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

堆栈现场如图 12 所示,根据 MAT 对 dump 数据的分析,发现最多的内存对象为 byte[] 和 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 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 之类的工具也可以在一定程度对系统性能瓶颈点进行快速定位。

但是一些与特定场景绑定或者业务数据绑定的情况,却需要辅助代码走查、性能检测工具、数据模拟甚至线上引流等方式才能最终确认性能问题的出处。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

架构学习资料

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

准备两个月,面试五分钟,Java中高级岗面试为何越来越难?

由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
构学习资料

[外链图片转存中…(img-pYEdbxAq-1712922552874)]

[外链图片转存中…(img-WpPDDOCA-1712922552874)]

[外链图片转存中…(img-62dw9Gsi-1712922552874)]

[外链图片转存中…(img-jJj0Nx7V-1712922552875)]

[外链图片转存中…(img-LsF7GveD-1712922552875)]

由于篇幅限制小编,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于 Linux 上的 Java 程序调,有几个常见的方面可以考虑: 1. JVM 参数调:可以通过调整 JVM 的参数来Java 程序的性能。例如,可以通过设置堆大小、垃圾回收器策略、线程池大小等参数来实现化。可以使用工具如 jstat、jvisualvm、jmap 等来监控和分析程序的运行情况。 2. 内存管理:Java 程序在运行时会消耗大量的内存,合理管理内存可以提高程序的性能。可以通过使用合适的数据结构、避免过度创建对象、及时释放资源等方式来进行内存管理。 3. 并发控制:多线程是 Java 程序的一个特点,但不正确的并发控制可能导致性能问题。可以使用并发集合类、合理使用锁、避免竞争条件等方式来化并发性能。 4. IO 操作化:IO 操作通常是影响 Java 程序性能的重要因素之一。可以使用非阻塞 IO、缓冲区、批量处理等方法来提高 IO 操作的效率。 5. 数据库访问化:如果 Java 程序需要频繁地访问数据库,可以考虑使用连接池、合理设计数据库表结构、使用索引、批量操作等方式来化数据库访问性能。 6. 代码化:对于性能瓶颈明显的代码片段,可以进行针对性的化。例如,使用更高效的算法、减少循环次数、避免不必要的计算等。 请注意,以上只是一些常见的化方面,具体的调方法需要根据具体情况进行分析和实施。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值