根据第一节所讲的基础知识,我们根据线上不同的异常情况做程序的优化。
CPU占用高
us高
根据之前的分析,CPU us高的原因主要是执行线程无任何挂起动作,且一直执行,导致CPU没有机会去调度执行其他的线程,造成线程饿死的现象。对于这种情况,常见的一种优化方法是对这种线程的动作增加Thread.sleep,以释放CPU的执行权,降低CPU的消耗。对于线程要不断扫描某种状态,达到自己的可继续运行下去的条件时再运行(像代码while(xxx==yyy)),使用wait/notifyAll会比较好。
sy高
CPU sy高的原因主要是线程的运行状态要经常切换,
另处一个原因是锁竞争激烈,造成线程状态切换。
方案:最简单的优化方法是减少线程数。若系统要支持大量并发的话,最好建立线程队列来缓解压力。
注意:降低线程数后,可能造成sy降低,us升高。
若无法降低线程数,可采用协程,以支持更高并发量。粒度上协程比线程小,线程不用频繁切换。(协程在java里还不太成熟,但是可以大胆往这方面尝试。)
文件IO消耗严重
从程序角度而言,造成文件IO消耗严重的原因主要是多个线程将大量的数据写到同一文件,导致文件很快变得很大,从而写入速度越来越慢,并造成各线程激烈争抢文件锁。
方案:
异步写文件,避免写入慢导致应用性能下降太多。(如日志采用log4j的AsyncAppender)
批量读写
限流,将读写IO降低到一个能接受的范围。(时间窗,停牌桶等)。
限制文件大小,大文件读或者追加写动作会增加耗时,限制文件大小后,超过一定值新生成文件。
网络IO消耗
方案:限流,控制发送频次。
内存消耗
释放不必要的引用。典型如ThreadLocal,在线程结束时,这里面的内容不会主动释放,会造成内存增大。查看!ThreadLocal使用不当后果
使用对象缓存池创建对象的实例要耗费一定的CPU以及内存,使用对象缓存池一定程度上可降低JVM Heap内存的使用。(慎用,使用不当会造成内存增大)。但是引入合理的缓存失效机制,会改善内存占用。如FIFO,LRU,LFU。
LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面。LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页。
合理使用SoftReference和WeakReference对于占据内存但又不是必须存在的对象,例如缓存对象,也可以基于SoftReference或WeakReference的方式来进行缓存。