java性能调优
一、代码优化
1、使用递归调用时,如果过多的调用容易造成java.lang.StackOverflowError即栈溢出和程序执行过慢。这是一个潜在Bug和影响程序执行效率问题,需要谨慎使用。原因:每次递归调用时会向栈中push当前方法的运行状态(现场),而Java栈内存的使用超过限制的大小时,程序会出现栈异常。
2、及时关闭流。Java编程过程中,进行数据库连接、I/O 流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。
3、注意方法、类文件中的代码量,适度分离。
4、使用基本类型定义变量时,千万注意该变量值可能为null的情况,此时建议使用对应的包装类来定义变量。
5、循环体内不要使用 “+” 进行字符串拼接,而直接使用 StringBuilder 不断 append。通过查看反编译以后的代码,可以发现,在字符串常量在拼接过程中,是将 String 转成了 StringBuilder 后,使用其 append() 方法进行处理的。
6、循环内不要不断创建对象引用。
7、尽量采用懒加载的策略,即在需要的时候才开始创建变量和对象等。
8、将一些需要变动的配置或文案写在属性文件中。
9、后端接口需要提供必要的校验,不要过于依赖前端校验。
10、基于效率和类型检查的考虑,应该尽可能使用 数组,无法确定数组大小时才使用 集合。
11、避免使用硬编码,多使用final定义常变量,提高可读性。
12、尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销。
13、不要创建一些不使用的对象,不要导入一些不使用的类。
14、尽量在合适的场合使用单例
使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
(1)控制资源的使用,通过线程同步来控制资源的并发访问
(2)控制实例的产生,以达到节约资源的目的
(3)控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
15、使用同步代码块替代同步方法。
尽量使用同步代码块,避免对那些不需要进行同步的代码也进行了同步,影响了代码执行效率。
16、在变与不变的过程中,访问权限控制真的太重要了。在写代码的过程中,我们需要尽量遵循这样一个原则——除了那些必须public的方法,尽量把其他方法定义为private。这样做的好处是,在重构private方法的时候不必再担惊受怕,因为它们不会被类外部访问到,而不必担心有没有对外界造成干扰。
二、数据库优化
适当的加索引,可以提高查询速度,索引的选择 一般来说,应该在这些列上创建索引:
第一、 在经常需要搜索的列上,可以加快搜索的速度;
第二、 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;
第三、 在经常用在连接的列上,这些列主要是一些外键,可以加快连接的速度;
第四、 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的;
第五、 在经常需要排序的列上创建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间;
第六、 在WHERE子句的列上面创建索引,加快条件的判断速度。
1、尽量使用简单的SQL,避免多表查询以及在SQL中处理复杂的逻辑。
2、查询时避免全表扫描,适度增加索引。首先应考虑在 where 及 order by 涉及的列上建立索引 ,一个表的索引数最好不要超过6个。
3、任何地方都要慎重使用 select * from t取出所有列 ,不要返回用不到的任何字段。
4、适当将复杂查询切分为若干个单表查询或简易查询。如将关联查询分解为若干个单表查询。多个表查询效率低,容易锁表和阻塞。
5、避免在WHERE 语句中对索引字段进行计算、类型转换、对字段进行 null 值判断等操作,导致索引完全没发挥作用。
6、GROUP BY和DISTINCT的使用。
DISTINCT方式就是两两对比,需要遍历整个表。GROUP BY分组类似先建立索引再查索引,所以两者对比,小表DISTINCT快,不用建索引。大表GROUP BY。
7、JOIN时使用小结果集驱动大结果集。
8、考虑使用“临时表”暂存中间结果
简化SQL语句的重要方法就是采用临时表暂存中间结果,但是,临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。 但是也得避免频繁创建和删除临时表,以减少系统表资源的消耗。
9、调整Where字句中的连接顺序
DBMS一般采用自下而上的顺序解析where字句,根据这个原理表连接最好写在其他where条件之前,那些可以 过滤掉最大数量记录。
10、使用表的别名
当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个列名上。这样就可以减少解析的时间并减 少哪些友列名歧义引起的语法错误。
11、更新Update语句优化
如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。
12、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引。
13、在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,
14、慎用distinct关键字
distinct在查询一个字段或者很少字段的情况下使用,会避免重复数据的出现,给查询带来优化效果。但是查询字段很多的情况下使用,则会大大降低查询效率。原因是当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据,然而这个比较,过滤的过程则会毫不客气的占用系统资源,cpu时间。
15、SQL语句中exists和in的区别
SELECT * FROM A WHERE id IN (SELECT id FROM B)
SELECT * FROM A a WHERE id EXISTS(SELECT 1 FROM B b WHERE a.id = b.id)
in 是在内存中遍历比较
exist 需要查询数据库,所以当B的数据量比较大时,exists效率优于in.
in()适合B表比A表数据小的情况
exists()适合B表比A表数据大的情况
通常情况下采用exists要比in效率高,因为IN不走索引,但要看实际情况具体使用: IN适合于外表大而内表小的情况;EXISTS适合于外表小而内表大的情况。
16、当表使用索引时,禁止 左模糊 或 全模糊 查询,否则不会命中索引。
17、除了掌握SQL基本的优化手段,使用explain、profile等工具来逐步调优。explain可以检查索引使用情况以及扫描的行。
三、缓存优化
分类
a、本地缓存(HashMap/ConcurrentHashMap、Ehcache、RocksDB、Guava Cache等)。
b、缓存服务(Redis/Tair/Memcache等)。
缓存算法(FIFO 、LRU、LFU三种算法)https://blog.csdn.net/youanyyou/article/details/78989956
1、什么时候更新缓存?如何保障更新的可靠性和实时性?
更新缓存的策略,需要具体问题具体分析。基本的更新策略有两个:
1) 接收变更的消息,准实时更新。
2) 给每一个缓存数据设置5分钟的过期时间,过期后从DB加载再回设到DB。这个策略是对第一个策略的有力补充,解决了手动变更DB不发消息、接收消息更新程序临时出错等问题导致的第一个策略失效的问题。通过这种双保险机制,有效地保证了缓存数据的可靠性和实时性。
2、缓存是否会满,缓存满了怎么办?
对于一个缓存服务,理论上来说,随着缓存数据的日益增多,在容量有限的情况下,缓存肯定有一天会满的。如何应对?
1) 给缓存服务,选择合适的缓存逐出算法,比如最常见的LRU。
2) 针对当前设置的容量,设置适当的警戒值,比如10G的缓存,当缓存数据达到8G的时候,就开始发出报警,提前排查问题或者扩容。
3) 给一些没有必要长期保存的key,尽量设置过期时间。
3、缓存是否允许丢失?丢失了怎么办?
根据业务场景判断,是否允许丢失。如果不允许,就需要带持久化功能的缓存服务来支持,比如Redis或者Tair。更细节的话,可以根据业务对丢失时间的容忍度,还可以选择更具体的持久化策略,比如Redis的RDB或者AOF。
缓存问题
1、缓存穿透
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决方案:
1) 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2) 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。
2、缓存击穿
描述:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
3、缓存雪崩
描述:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿是并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
1)缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2)如果缓存系统是分布式部署,将热点数据均匀分布在不同的缓存节点中。
3)设置热点数据永远不过期。
4、缓存更新
Cache Aside 模式:这是最常用最常用的pattern了。其具体逻辑如下:
失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。
更新:先把数据存到数据库中,成功后,再让缓存失效。
四、JVM优化
后端语言是java,对JVM进行优化也能一定程度上的提升java程序的性能。JVM通常能够在软件开发后期进行,如在开发完毕或者是软件开发的某一里程碑阶段,JVM的各项參数将会直接影响JAVA程序的性能。
性能指标
关注以下指标:CPU使用率、CPU load、GC count、GC time、GC日志
查看java进程GC状态:jstat -gcutil {pid} 1000
查看java进程CPU高原因:
1) 获取java进程pid:ps –ef|grep java
2) 分析是哪个线程占用率过高:top -H -p ‘PID’
3) 线程id转换为16进制:printf “%x\n” ‘NID’
4) Jstack查看线程堆栈:jstack PID | grep ‘NID’ -C行数 –color
推荐2个java工具:1)show-busy-java-threads 2)arthas
优化方向
比如,JVM的堆大小(Xms、Xmx),垃圾回收策略等。要进行JVM层面的调优,需要对JVM的执行原理有一定的了解,如内存的结构,GC的种类等,然后根据应用程序的特点设置合理的JVM參数,但是GC tuning is the last task to be done.
https://www.cnblogs.com/csniper/p/5592593.html(详细内容)
五、性能分析工具
Java性能分析神器-JProfiler
1、使用方便
2、界面操作友好
3、对被分析的应用影响小
4、CPU,Thread,Memory分析功能尤其强大
5、支持对jdbc,noSql, jsp, servlet, socket等进行分析
6、支持多种模式(离线,在线)的分析
7、跨平台
来源:CSDN
作者:Mr-dream
链接:https://blog.csdn.net/hjjdehao/article/details/104015248