1、 衡量程序性能的指标
可以从常用的性能评估指标入手:
- 并发:同一时间有多少请求访问
- TPS:transaction per second(每秒的事物数)
- QPS:query per second(每秒请求数)
- 耗时:端到端耗时,服务端耗时,应用程序耗时
- 95线:95%的请求落在什么范围内
- 99线:99%的请求落在什么范围内
2、Java 程序性能优化切入点
-
硬件优化:增加 CPU、内存、磁盘等硬件资源,提高程序运行效率。
-
JVM 参数优化:调整 JVM 参数,包括堆大小、垃圾回收机制等,提高 JVM 性能。
-
代码优化:包括算法优化、数据结构优化、避免重复计算、复用优化、结果集优化(JSON)、资源冲突优化等。
-
计算优化:使用多线程技术,变同步为异步;惰性加载(使用设计模式优化业务),提高程序性能。
-
数据库优化:包括索引优化、SQL 语句优化等,提高数据库查询效率。
-
网络优化:包括减少网络传输数据量、使用缓存等,提高网络传输效率。
-
缓存优化:使用缓存技术,减少程序对数据库等资源的访问,提高程序响应速度。
-
日志优化:包括日志级别、日志格式等,减少不必要的日志输出,提高程序性能。
3、实用的性能优化手段
3.1、JVM调优
自动内存管理模型:
(1)程序计数器
一块较小的内存空间,可以看出当前线程所执行的字节码的行号指示器。在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖程序计数器来实现。线程私有。
(2)Java虚拟机栈
线程私有,生命周期与线程相同。虚拟机栈描述的Java方法(也就是字节码)执行的线程内存模型:每个方法执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表(局部变量表存放了编译器可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向了一条字节码指令的地址))、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
(3)本地方法栈
为执行本地(Native)方法服务。线程私有。
(4)Java堆
JVM垃圾回收器管理的最大的一块内存空间,线程共享,存放了几乎所有的实列对象。Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”。
(5)方法区
线程共享,元空间。存储用于已经被Java虚拟机加载的类型信息、常量、即时编译器编译后的代码缓存等数据。
JDK8:永久代被元空间所替代。
a.运行时常量池
方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项是常量池表(Constant Pool Table),用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
(6)直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内容也被频繁使用,而且也可能导致OutOfMemoryError异常出现。
在JDK1.4中引入了NIO(New Intput/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)实现的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆之间来回复制数据。
本机直接内存不会受到Java堆大小的限制,但是会受到本机总内存(包括物理内存、SWAP分区或分页文件)大小及处理器寻址空间的限制。一般服务器管理员在配置虚拟机参数时,会根据实际内存配置Xmx等参数,往往忽略掉直接内存,导致各内存区域总和大于物理内存区域限制(包括物理和操作系统及级的限制),从而导致动态扩展时出现OutOfMemorryError异常。
1、内存大小的取舍
(1)扩大内存可以降低触发GC的次数
(2)内存太大触发GC时的停顿时间也会太长
因此要根据实际的业务场景设置成一个“合适”的值,并配合压测和线上环境的实际情况不断的调优,建议:吞吐量=花费在非GC停顿上的工作时间/总时间>95%。
控制内存大小的核心JVM参数:
-
-Xms:启动JVM时堆内存的大小
-
-Xmx:堆内存最大限制
建议:两者需要设置的一样防止扩缩容 -
-XX:NewSize 年轻代大小
-
-XX:MaxNewSize 最大年轻代大小
建议:两者需要设置的一样防止扩缩容 -
-XX:SurvivorRation Eden与Survivor 占比,默认为8
建议:Eden需要必Survivor尽可能的大(至少是一倍),防止多次触发young gc导致年龄快速增长到可以进入老年代的case -
-XX:MetaspaceSize 元空间初始空间大小
-
-XX:MaxMetaspaceSize=512 元空间最大空间,默认是没有限制的。
2、GC优化策略
- 将进入老年代的对象减少到最低
- young gc: 40ms内
- major gc:(老年代): stop the world时间总和控制在100ms内
- full gc: 尽可能少,且时间在1s内
除了cms和g1这两种GC收集器外,其余的major gc=full gc
GC策略开启参数
JVM调优标志,除了少数例外,JVM接收两种标志:
布尔标志和附带参数的标志。布尔标志使用的语法是:
-XX:+FlagName表示开启,-XX:-FlagName表示关闭。
附带参数的标志使用的语法是:-XX:FlagName=something,表示设置FlagName的值为something。其中,something指表示任意值的符号。例如,-XX:NewRatio=N表示NewRatio标志可以设成任意值N(N的含义将是讨论的重点)。
以CMS为例,看一下具体的参数配置:
(1)CMS发生Full GC条件 - Promotion failure:由于内存碎片导致的晋升空间不足
- Concurrent mode failed:还未完成cms又触发了下一次major gc
(2)CMS调优
3.2、应用程序优化
1、日志优化
- 同步日志/异步日志
- 日志归档时间
- 日志大小拆分
2、池化策略
- 核心线程数:
动态获取CPU核数方法:
Runtime.getRuntime().availableProcessors()
- CPU密集型:CPU核数+1
- IO密集型:CPU核数*2
3.3、提高数据库读写性能
1、单机数据库
(1) 查询优化
主键查询:千万条记录 1-10ms
唯一索引:千万条记录 10-100ms
非唯一索引:千万条记录 100-1000ms
无索引:百万条记录 1000ms+
(2)批量写优化
for each{insert into table values(1)},性能极差
Exeute once insert into table values (1),(2),(3),(4)…;
批量写优势:
Sql编译N次=>1次的时间与空间复杂度有很大的性能差异
网络消耗的时间复杂度大幅降低
磁盘寻址的复杂度大幅降低
(3)索引优化
(4) innodb相关优化
2、分布式部署
(1)读写分离
- 一主多从
- 读库延迟处理
- 主从切换处理
(2)分库分表
- 垂直拆分
- 水平拆分
- 多主多从
3.4、缓存
缓存穿透、击穿、雪崩…
4、推荐书籍