美国上市公司,专注Java培训21年

java开发流程中调优技巧有哪些?


java开发流程中调优技巧有哪些?java开发中调优是需要做好准备工作的,因为每一个应用的业务目标不相,同事性能瓶颈不会总在同一个点上。所以学会方法,根据实际情况作调整很重要。接下来我们说说对于调优这个事情来说,分为三个过程:

java开发流程中调优技巧

java开发流程调优技巧一:性能监控

问题没有发生,你并不知道你需要调优什么?此时需要一些系统、应用的监控工具来发现问题。

java开发流程调优技巧二:性能分析

问题已经发生,但是你并不知道问题到底出在哪里。此时就需要使用工具、经验对系统、应用进行瓶颈分析,以求定位到问题原因。

java开发流程调优技巧三:性能调优

经过上一步的分析定位到了问题所在,需要对问题进行解决,使用代码、配置等手段进行优化。

Java调优也不外乎这三步。此外,性能分析、调优等是抛开以下因素的:

系统底层环境:硬件、操作系统等

数据结构和算法的使用

外部系统如数据库、缓存的使用

调优准备:

调优是需要做好准备工作的,毕竟每一个应用的业务目标都不尽相同,性能瓶颈也不会总在同一个点上。在业务应用层面,我们需要:

需要了解系统的总体架构,明确压力方向。比如系统的哪一个接口、模块是使用率最高的,面临高并发的挑战。

需要构建测试环境来测试应用的性能,使用ab、loadrunner、jmeter都可以。

对关键业务数据量进行分析,这里主要指的是对一些数据的量化分析,如数据库一天的数据量有多少;缓存的数据量有多大等

了解系统的响应速度、吞吐量、TPS、QPS等指标需求,比如秒杀系统对响应速度和QPS的要求是非常高的。

了解系统相关软件的版本、模式和参数等,有时候限于应用依赖服务的版本、模式等,性能也会受到一定的影响。

性能分析:

在系统层面能够影响应用性能的一般包括三个因素:CPU、内存和IO,可以从这三方面进行程序的性能瓶颈分析。

CPU分析

当程序响应变慢的时候,首先使用top、vmstat、ps等命令查看系统的cpu使用率是否有异常,从而可以判断出是否是cpu繁忙造成的性能问题。其中,主要通过us(用户进程所占的%)这个数据来看异常的进程信息。当us接近100%甚至更高时,可以确定是cpu繁忙造成的响应缓慢。一般说来,cpu繁忙的原因有以下几个:

线程中有无限空循环、无阻塞、正则匹配或者单纯的计算

发生了频繁的gc

多线程的上下文切换

内存分析:

对Java应用来说,内存主要是由堆外内存和堆内内存组成。

1. 堆外内存堆外内存主要是JNI、Deflater/Inflater、DirectByteBuffer(nio中会用到)使用的。对于这种堆外内存的分析,还是需要先通过vmstat、sar、top、pidstat等查看swap和物理内存的消耗状况再做判断的。此外,对于JNI、Deflater这种调用可以通过Google-preftools来追踪资源使用状况。

2. 堆内内存此部分内存为Java应用主要的内存区域。通常与这部分内存性能相关的有:

创建的对象:这个是存储在堆中的,需要控制好对象的数量和大小,尤其是大的对象很容易进入老年代

全局集合:全局集合通常是生命周期比较长的,因此需要特别注意全局集合的使用

缓存:缓存选用的数据结构不同,会很大程序影响内存的大小和gc

ClassLoader:主要是动态加载类容易造成永久代内存不足

多线程:线程分配会占用本地内存,过多的线程也会造成内存不足

O分析:

通常与应用性能相关的包括:文件IO和网络IO。

文件IO可以使用系统工具pidstat、iostat、vmstat来查看io的状况。这里可以看一张使用vmstat的结果图。

这里主要注意bi和bo这两个值,分别表示块设备每秒接收的块数量和块设备每秒发送的块数量,由此可以判定io繁忙状况。进一步的可以通过使用strace工具定位对文件io的系统调用。通常,造成文件io性能差的原因不外乎:

大量的随机读写

设备慢

文件太大

网络IO查看网络io状况,一般使用的是netstat工具。可以查看所有连接的状况、数目、端口信息等。例如:当time_wait或者close_wait连接过多时,会影响应用的相应速度。

性能调优

与性能分析相对应,性能调优同样分为三部分。

CPU调优:

不要存在一直运行的线程(无限while循环),可以使用sleep休眠一段时间。这种情况普遍存在于一些pull方式消费数据的场景下,当一次pull没有拿到数据的时候建议sleep一下,再做下一次pull。

轮询的时候可以使用wait/notify机制

避免循环、正则表达式匹配、计算过多,包括使用String的format、split、replace方法(可以使用apache的commons-lang里的StringUtils对应的方法),使用正则去判断邮箱格式(有时候会造成死循环)、序列/反序列化等。

结合jvm和代码,避免产生频繁的gc,尤其是full GC。

此外,使用多线程的时候,还需要注意以下几点:

使用线程池,减少线程数以及线程的切换

多线程对于锁的竞争可以考虑减小锁的粒度(使用ReetrantLock)、拆分锁(类似ConcurrentHashMap分bucket上锁), 或者使用CAS、ThreadLocal、不可变对象等无锁技术。此外,多线程代码的编写最好使用jdk提供的并发包、Executors框架以及ForkJoin等,此外Discuptor和Actor在合适的场景也可以使用。

内存调优:

内存的调优主要就是对jvm的调优。

合理设置各个代的大小。避免新生代设置过小(不够用,经常minor gc并进入老年代)以及过大(会产生碎片),同样也要避免Survivor设置过大和过小。

选择合适的GC策略。需要根据不同的场景选择合适的gc策略。这里需要说的是,cms并非全能的。除非特别需要再设置,毕竟cms的新生代回收策略parnew并非最快的,且cms会产生碎片。此外,G1直到jdk8的出现也并没有得到广泛应用,并不建议使用。

jvm启动参数配置-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:[log_path],以记录gc日志,便于排查问题。

其中,对于第一点,具体的还有一点建议:

年轻代大小选择:响应时间优先的应用,尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生gc的频率是最小的。同时,也能够减少到达年老代的对象。吞吐量优先的应用,也尽可能的设置大,因为对响应时间没有要求,垃圾收集可以并行进行,建议适合8CPU以上的应用使用。

年老代大小选择:响应时间优先的应用,年老代一般都是使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:

并发垃圾收集信息

持久代并发收集次数

传统GC信息

花在年轻代和年老代回收上的时间比例

一般吞吐量优先的应用都应该有一个很大的年轻代和一个较小的年老代。这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代存放长期存活对象。

此外,较小堆引起的碎片问题:因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现“碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现“碎片”,可能需要进行如下配置:-XX:+UseCMSCompactAtFullCollection,使用并发收集器时,开启对年老代的压缩。同时使用-XX:CMSFullGCsBeforeCompaction=xx设置多少次Full GC后,对年老代进行压缩。

其余对于jvm的优化问题可见后面JVM参数进阶一节。

关于java开发流程中调优技巧还有很多细节,篇幅有限这里小编就不一一详说了,想了解跟多java开发流程中调优技巧知识可以通过小编分享的内容继续往下拓展延伸,希望能帮到你!


【免责声明】本文部分系转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,如涉及作品内容、版权和其它问题,请在30日内与我们联系,我们会予以重改或删除相关文章,以保证您的权益!

Java开发高端课程免费试学

大咖讲师+项目实战全面提升你的职场竞争力

  • 海量实战教程
  • 1V1答疑解惑
  • 行业动态分析
  • 大神学习路径图

相关推荐

更多

Java开班时间

收起