1.为什么要做性能调优
所有的系统在开发完之后,多多少少都会有性能问题,我们首先要做的就是想办法把问题暴露出来,例如进行压力测试、模拟可能的操作场景等等,再通过性能调优去解决这些问题。好的系统性能调优不仅仅可以提高系统的性能,还能为公司节省资源。
2.什么时候开始性能调优呢
其实,在项目开发的初期,我们没有必要过于在意性能优化,这样反而会让我们疲于性能优化,不仅不会给系统性能带来提升,还会影响到开发进度,甚至获得相反的效果,给系统带来新的问题。
在项目成功上线后,我们还需要根据线上的实际情况,依照日志监控以及性能统计日志,来观测系统性能问题,一旦发现问题,就要对日志进行分析并及时修复问题。
3.影响性能的因素
CPU:有的应用需要大量计算,他们会长时间、不间断地占用 CPU 资源,导致其他资源无法争夺到 CPU 而响应缓慢,从而带来系统性能问题。例如,代码递归导致的无限循环,正则表达式引起的回溯,JVM 频繁的 FULL GC,以及多线程编程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙。
内存:Java 程序一般通过 JVM 对内存进行分配管理,主要是用 JVM 中的堆内存来存储 Java 创建的对象。系统堆内存的读写速度非常快,所以基本不存在读写性能瓶颈。但是由于内存成本要比磁盘高,相比磁盘,内存的存储空间又非常有限。所以当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问题。
磁盘 I/O:磁盘相比内存来说,存储空间要大很多,但磁盘 I/O 读写的速度要比内存慢,虽然目前引入的 SSD 固态硬盘已经有所优化,但仍然无法与内存的读写速度相提并论。
数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性。对于有大量数据库读写操作的系统来说,数据库的性能优化是整个系统的核心。
网络:网络对于系统性能来说,也起着至关重要的作用。如果你购买过云服务,一定经历过,选择网络带宽大小这一环节。带宽过低的话,对于传输数据比较大,或者是并发量比较大的系统,网络就很容易成为性能瓶颈。
4.性能的指标
1.响应时间
响应时间是衡量系统性能的重要指标之一,响应时间越短,性能越好,一般一个接口的响应时间是在毫秒级。在系统中,我们可以把响应时间自下而上细分为以下几种:
数据库响应时间:数据库操作所消耗的时间,往往是整个请求链中最耗时的;
服务端响应时间:服务端包括 Nginx 分发的请求所消耗的时间以及服务端程序执行所消耗的时间;
网络响应时间:这是网络传输时,网络硬件需要对传输的请求进行解析等操作所消耗的时间;
客户端响应时间:对于普通的 Web、App 客户端来说,消耗时间是可以忽略不计的,但如果你的客户端嵌入了大量的逻辑处理,消耗的时间就有可能变长,从而成为系统的瓶颈。
2.吞吐量
系统吞吐量是指在单位时间内中央处理器(CPU)从存储设备读取、处理、存储信息的量。吞吐量的大小主要由网卡的处理能力、内部程序算法以及带宽大小决定。
3.TPS
每秒传输的事物处理个数(Transactions Per Second),即服务器每秒处理的事务数。TPS包括一条消息入和一条消息出,加上一次用户数据库访问,TPS是软件测试结果的测量单位。一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程。客户机在发送请求时开始计时,收到服务器响应后结束计时,以此来计算使用的时间和完成的事务个数。
4.QPS
每秒查询率(Queries Per Second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。
5.性能优化的步骤
- 目前现象
- 提出猜想
- 验证猜想
- 定位到问题
- 解决问题
6.性能检测的工具JMeter
JMeter 是 Apache 提供的一款功能性比较全的性能测试工具,同样可以在 Windows 和 Linux 环境下安装使用。JMeter 在 Windows 环境下使用了图形界面,可以通过图形界面来编写测试用例,具有易学和易操作的特点。
JMeter 不仅可以实现简单的并发性能测试,还可以实现复杂的宏基准测试。我们可以通过录制脚本的方式,在 JMeter 实现整个业务流程的测试。JMeter 也支持通过 csv 文件导入参数变量,实现用多样化的参数测试系统性能。
Windows 下的 JMeter 安装非常简单,在官网下载安装包,解压后即可使用(一般只需要JDK的环境)。如果你需要打开图形化界面,那就进入到 bin 目录下,找到 jmeter.bat 文件,双击运行该文件就可以了。
7.常用性能优化手段
代码优化
算法与数据结构优化
网络优化
JVM优化
数据库优化
场景优化
8.代码优化
1.不需要实例化的类应该构造器私有
一些工具类提供的都是静态方法,这些类是不应该提供具体的实例的。可以参考JDK中的Arrays。好处:防止使用者new出多个实例。如果是自己写的工具类,可以把构造器私有化
2.不要创建不必要的对象
如在一些方法内部定义局部变量时,遇到 int long float等类型是尽量用基本数据类型,不用包装类型。
可以在类的多个实例之间重用的成员变量,尽量使用static。
3.使类和成员的可访问性最小化
模块对外部其他模块来说,隐藏其内部数据和其他实现细节—封装。编写程序和设计架构,最重要的目标之一就是模块之间的解耦。使类和成员的可访问性最小化无疑是有效的途径之一。比如HashMap中使用内部类Node节点
4.使可变性最小化
尽量使类不可变,不可变的类比可变的类更加易于设计、实现和使用,而且更不容易出错,更安全。
常用的手段:
不提供任何可以修改对象状态的方法;
使所有的域都是final的。
使所有的域都是私有的。
5.组合优先于继承
继承容易破坏封装性,而且会使子类的实现依赖于父类。
组合则是在类中增加一个私有域,引用类的一个实例,这样的话就避免了依赖类的具体实现。
6.返回零长度的数组或集合,不要返回null
方法的结果返回null,会导致调用方的要单独处理为null的情况。返回零长度,调用方可以统一处理,如使用foreach即可。JDK中也为我们提供了Collections.EMPTY_LIST这样的零长度集合。
7.精确计算,避免使用float和double
float和double在JVM存储的时候,有部分要做整数位,有部分要做小数位,所以存在精度上的问题 可以使用int或者long以及BigDecimal。
8.控制方法的大小
单个方法尽量控制在1~1.5个屏幕,最好不要超过80行
1.与JVM中的内容JIT有关
2.便于阅读