- 浏览: 94672 次
- 性别:
- 来自: 深圳
文章分类
最新评论
5.2 调优
找出性能瓶颈后,接下来就是调优,调优通常可从硬件,操作系统、 JVM 以及程序四方面着手,硬件和操作系统不是本书的重点。下面主要介绍 JVM 及程序方面的一些调优。
5.2.1 JVM 调优
JVM 调优主要是内存管理方面的调优,包括各个代的大小、 GC 策略等。
- 代大小的调优
通常 minor GC 会远快于 Full GC, 各个代大小的设置直接决定了 minor GC 和 Full GC 触发的时机。在代的大小调优上,最关健的参数有:
-Xms –Xmx 决定了 JVM Heap 所能使用的最大空间,生产环境下通常设置为相同值,避免运行时刻动态扩展 JVM 内存空间
-Xmn 决定新生代空间,新生代中 Eden 、 S0 、 S1 三个区域的比率可通过 -XX:SurvivorRatio 来控制
-XX : MaxTemuringThreshold 控制对象在经历过多少次 Minor GC 后才转入旧生代,只有在串行 GC 时有效,其它 GC 方式时由 Sun JDK 自行决定。
- GC 策略的调优
作者对并行 GC 和并发 GC (串行 GC 性能太差,不考虑)的测试结果, CMSCompactatFullCollection 明显要比 ParallelGC 效果好,对应用造成的停顿时间短。
目前内存管理方面, JVM 自身已经做得非常不错了,因此如果不是有确切的 GC 造成性能低的理由,就没必要做过多细节方面的调优。多数情况下只须选择 GC 策略并设置 JVM Heap 的大小即可。此外,应关注 JDK 的新版本对性能方面的提升。
5.2.2 程序调优
CPU 消耗严重的解决方法
1. CPU us 高
根据之前的分析, CPU us 高的原因主要是执行线程无任何挂起动作,一直执行,导致 CPU 没有机会去调度执行其它的线程,造成线程饿死。对这种情况,常用方法是对这种线程的动作增加 Thread.sleep ,释放 CPU 的执行权,降低消耗。
实际的 JAVA 应用有很多类似场景,如多线程的任务执行管理器,它通常通过扫描任务集合列表来执行任务。
另一种经典的场景是状态的扫描。例如某线程要等其它线程改变了值后才可继续执行,此时应采用 wait/notify 机制。
其它如循环次数太多,正则,计算等造成的 CPU us 过高的状况,则要结合业务场景来调优。
对于 GC 频繁造成的 CPU us 过高的现象,则要通过 JVM 调优或程序调优,降低 GC 执行次数。
2. CPU sy 高
主要原因是线程的运行状态要经常切换,此种情况,最简单的办法是减少线程数。
另一个重要原因是线程之间锁竞争激烈,锁竞争更有可能造成系统资源消耗不多,但系统性能不足的现象,降低锁竞争的调优技巧将在后续章节进行讲述。
除以上两种情况外,对于分布式 JAVA 应用而言,还有一种典型现象是应用中有较多的网络 IO 或确实需要一些锁竞争机制,例如数据库连接池,但为了能够支撑高的并发量,在 JAVA 应用中又只能借助启动更多的线程来支撑,这种情况下当并发量达到一定程度后,可能会造成 CPU sy 高的现象,可采用协程 (Coroutine) 来解决。在 JAVA 中目前主要可用现实现协程的框架为 Kilim ,目前 Kilim 的版本仅为 0.7 ,且没有商用的实际例子,要使用还需慎重。
JDK7 也有一个支持协程方式的实现,感兴趣的读者可进一步阅读。
文件 IO 消耗严重的解决方法
从程序角度而言, 要原因是多个线程在写大量的数据到同一文件,导致文件很快变得很大,导致写入速度越来越慢,并造成各线程激烈争抢文件锁。对这类情况,常用的调优办法有如下几种
- 异步写文件
如写日志,可用 log4j 提供的 AsyncAppender
- 批量读写
- 限流
还是用写日志作例子,当出现大量异常时,会出现所有的线程都在执行 log.error(…). 此时可采取一个简单的策略为统计一段时间内的 log.error 的执行频率。当超过这个频率时,一段时间内不再写 log ,或塞入一个队列后缓慢地写。
- 限制文件大小
此外,应尽可能地采用缓冲区等方式来读取文件内容,避免不断与操作系统交互,具体可参见 SUN 官方的关于 JAVA 文件 IO 优化的文章。
网络 IO 消耗严重的解决方法
主要原因是同时需要发送或接收的包太多,常用的调优方法为进行限流,限制发送 packet 的频率,从而在网络 IO 可接受的情况下来发送 packet.
对于内存消耗严重的情况
JVM Heap 内存消耗严重时,常用的程序调优方法有:
1. 释放不必要的引用
内存消耗严重的情况中最典型的一种现象是代码中持有了不需要的对象引用,造成对象无法被 GC ,最典型的一个例子是在利用线程的情况下使用 ThreadLocal, 由于线程复用, ThreadLocal 中存放的对象如未做主动释放的话则不会被 GC 。
- 使用对象缓存池
- 采用合理的缓存失效算法
- 合理使用 SoftReference 和 WeakReference
对于占据内存但又不是必须存在的对象,例如缓存对象,也可以基于 SoftReference 和 WeakReference 的方式来进行缓存,前者会在内存不够用的时候回收,后者会在 Full GC 的时候回收。
5.2.3 对于资源消耗不多,但程序执行慢的情况
对于分布式 JAVA 应用而言,造成这种情况的主要原因通常有锁竞争激烈及未充分利用硬件资源两种情况
锁竞争激烈
1. 使用并发包的类
并发包中的类多数都采用了 lock-free 、 nonblocking 算法,减少了多线程情况下资源的锁竞争。如并发包中的类无法满足需求时,可参考学习一些 nonblocking 算法来自行实现。 Nonblocking 算法的机制,为基于 CAS 来做到无需 lock 就可实现资源一致性的保证,主要的实现 nonblocking 算法有:
2. 使用 Treiber 算法
Treiber 算法主要用于实现 Stack ,基于 Ttrber 算法实现的无阻塞的 Stack 代码如下:
public class ConcurrentStack<E> { AtomicReference<Node<E>> head = new AtomicReference<Node<E>>();
public void push(E item) { Node<E> newHead = new Node<E>(item); Node<E> oldHead; do { oldHead = head.get(); newHead.next = oldHead; } while (!head.compareAndSet(oldHead, newHead)); }
public E pop() { Node<E> oldHead; Node<E> newHead; do { oldHead = head.get(); if (oldHead == null) return null; newHead = oldHead.next; } while (!head.compareAndSet(oldHead,newHead)); return oldHead.item; }
static class Node<E> { final E item; Node<E> next;
public Node(E item) { this.item = item; } } } |
以上代码摘自IBM DeveloperWorks 网站上的Java theory and practice 系统文章,由于Stack 是LIFO 方式,因此不能采取类似LinkedBlockingQueue 中两把锁的机制。这里巧妙地采用AtomicReference 来实现了无阻塞的push 和pop ,在push 时基于AtomicReference 的CAS 方法来比较目前的head 是否一致。如不一致,说明有其它线程改动了,如有改动则继续循环,直到一致,才修改head 元素,在pop 时采用同样的方式进行操作。
3. 使用 Michael-Scott 非阻塞队列算法
和Treiber 算法类似,也是基于CAS 以及AtomicReference 来实现队列的非阻塞操作,ConcurrentLinkedQueue 就是典型的基于Michael-Scott 实现的非阻塞队列。
从上面两种算法来看,基于CAS 和AtomicReference 来实现无阻塞算法是不错的选择。但值得注意的是,由于CAS 是基于不断的循环比较来保证资源一致性的,对于冲突较多的应用场景而言,CAS 会带来更高的CPU 消耗,因此不一定采用CAS 实现无阻塞的就一定比采用Lock 方式的性能好。业界还有一些无阻塞算法的改进,如MCAS 、WSTM20 等
4. 尽可能少用锁
尽可能让锁仅在需要的地方出现,通常没必要对整个方法加锁,而只对需要控制的资源做加锁操作。尽可能让锁最小化,例如一个操作中需要保护的资源只有HashMap, 那么在加锁时则可只synchronized(map) ,而没必要synchronized(this).
5. 拆分锁
把独占锁拆分为多把锁,常见的有读写锁拆分及类似ConcurrentHashMap 中默认拆分为16 把锁的方法。需要注意的是采用拆分锁后,全局性质的操作会变得比较复杂,例如ConcurrentHashMap 的size 操作。
6. 去除读写操作的互斥锁
在修改时加锁,并复制对象进行修改,修改完后切换对象的引用,而读取操作时则不加锁,这种方式称为CopyOnWrite 。CopyOnWriteArrayList 就是其中的典型实现。这种做法好处是可以明显提升读的性能,适用读多写少的场合,坏处是造成更多的内存消耗。
未充分使用硬件资源
1. 未充分使用CPU
对于 JAVA 应用而言,通常原因就是在能并行处理的场景中未使用足够的线程。
另外,单线程的计算,也可以拆分为多线程来分别计算,最后合并结果, JDK7 中的 fork-join 框架可以给以上场景提供一个好的支撑方法
2. 未充分使用内存
如数据的缓存、耗时资源的缓存(如数据库连接,网络连接)、页面片段的缓存等。
对于数据量大造成的性能不足,在第 7 章“构建可伸缩的系统”中提供了一些优化方案。从纯粹的软件调优角度来讲,充分而不过分使用硬件资源,合理调整 JVM 以及合理使用 JDK 包是调优的三大有效原则,调优没有银弹,结合系统现状和多尝试不同的调优策略是找到合适的调优方法的唯一途径。
发表评论
-
技术文章精华合集(持续更新中)
2016-09-20 19:09 717Kafka深度解析 分库分表系列文章 来自 ... -
<<More Joel on Software>> 如何扮演程序经理的角色
2011-07-03 15:13 797制作伟大软体的秘方之 ... -
<<More Joel on Software>> 利诱管理法
2011-07-03 15:10 959利诱管理法 Joke: A poor J ... -
<<More Joel on Software>> The Joel Test: 软件开发成功 12 法则
2011-07-03 14:42 689作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 看起来简单, 实际上复杂
2011-07-03 14:41 761作者: 周思博 (Joel Spolsky) 译: Bo Y ... -
<<More Joel on Software>> 膨胀软件与80/20的谣传
2011-07-03 14:38 885作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 每日构建(daily build)是你的朋友
2011-07-03 14:37 852作者: 周思博 (Joel Spols ... -
<<More Joel on Software>> 五个为什么
2011-07-03 14:20 957五个为什么(译文) ... -
<<More Joel on Software>> 飙高音
2011-07-03 13:54 906飙高音(译文) 作者: 阮一峰 日期: 2009年 ... -
<<More Joel on Software>> 关于战略问题的通信之六
2011-07-03 13:51 769关于战略问题的通信之六(译文) 作者: 阮一峰 日 ... -
<<More Joel on Software>> 易用性是不够的
2011-07-03 13:41 738易用性是不够的(译文) 作者: 阮一峰 日期: 2 ... -
<<More Joel on Software>> 军事化管理法
2011-07-03 13:28 963高科技公司能否采用军事化管理?(译文) 作者: 阮一峰 ... -
<<More Joel on Software>> 寻找优秀的程序员
2011-07-03 13:24 1197=================== 寻找 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第七章 构建可伸缩的系统
2011-05-29 12:09 1105通常将通过升级或增加单机机器的硬件来支撑访问量及数据量增长的方 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第六章 构建高可用的系统
2011-05-28 11:02 1200对于互联网或企业中的大型应用而言,多数要求做到 7*24 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第五章 性能调优(一)
2011-05-22 13:43 11345.1 寻找性能瓶颈 通常性能瓶颈的 ... -
读书笔记:《分布式JAVA应用 基础与实践》 第四章 分布式JAVA应用与JDK类库
2011-05-21 11:23 11774.1 集合包 ArrayList, Li ... -
读书笔记:《分布式JAVA应用 基础与实践》 第三章 3.3 JVM线程资源同步及交互机制(二)
2011-05-17 21:52 938接下来网上没有,貌似 ... -
《分布式JAVA应用 基础与实践》 第三章 3.3 JVM线程资源同步及交互机制(一)
2011-05-17 19:09 8903.3 JVM线程资源同步及交互机制 Java程序采用 ... -
《分布式JAVA应用 基础与实践》 第三章 3.2 JVM内存管理(四)
2011-05-17 19:00 9253.2.4 JVM内存状况查看 ...
相关推荐
Java分布式应用学习笔记01分布式Java应用和SOA
Java分布式学习笔记01分布式Java应用
linux运维笔记:分布式文件系统GlusterFS.docx
性能优化手册是一套java性能学习研究小技巧,包含内容:Java性能优化、JVM性能优化、服务器性能优化、数据库性能优化、前端性能优化等。 内容包括但不限于: String 性能优化的 3 个小技巧 HashMap 7 种遍历方式...
ORACLE DBA工作笔记 运维数据迁移与性能调优 清晰版 。。。
Java分布式应用学习笔记
大型分布式网站架构设计与实践+笔记.zip 1.Cache缓存 2.持久化存储 3.消息系统MQ 4.垂直化搜索引擎 5.其他基础设施
Java 虚拟机学习笔记: Java 内存区域, 垃圾收集, 内存分配与回收策略, JVM 调优, 文件结构, 类加载机制, Java 程序 Java是一种面向对象的编程语言,由Sun Microsystems于1995年推出。它是一种跨平台的语言,...
文是学习大型分布式网站架构的技术总结。对架构一个高性能,高可用,可伸缩,可扩展的...一部分为读书笔记,一部分是个人经验总结。对大型分布式网站架构有很好的参考价值。(如果感觉对大家有帮助,请帮忙点推荐,谢谢
这篇文章主要讲述分布式时代和中间件相关知识,包括服务化、HSF、Notify和TDDL。同时里面有我们经常遇见的编码错误等相关问题,希望文章对你有所帮助!在系统发展的过程中,架构师的眼光至关重要,作为程序员,只要把...
读书笔记:第一章电子商务基础知识.pdf
自己的读书笔记,这是《大型网站系统与Java中间件实践》的第一章的读书笔记。主要讲解了分布式系统的基本知识。
Java分布式应用学习笔记07线程池应用
Java基础笔记, 第一章:编程基础 第二章:数组 第三章:面向对象程序开发 第四章:异常机制 第五章:多线程技术 第六章:常用类API 第七章:集合框架(容器)+其他类对象使用 第八章:IO流 第九章:GUI编程 第十章...
ORACLE学习笔记:日常应用、深入管理、性能优化.part1
Oracle性能调优笔记
学生读书笔记共享-学生读书笔记共享系统-学生读书笔记共享系统源码-学生读书笔记共享管理系统-学生读书笔记共享管理系统java代码-学生读书笔记共享系统设计与实现-基于springboot的学生读书笔记共享系统-基于Web的...
读书笔记 类型: 课程学习 名称: 计算机应用基础 时间:2006.7.7 体裁:说明文 " 知识内容与结构 "备 注 " "一.知识整体结构层次1说明: " " "计算机基础知识 " " "计算机软件 (系统软件——操作系统) " " ...