Java 程序性能优化


程序员典藏大系 JJaavvaa 程程序序性性能能优优化化 ——让你的 Java 程序更快、更稳定 葛一鸣 等编著 清 华 大 学 出 版 社 北 京 内 容 简 介 Java 是目前应用最为广泛的软件开发平台,学习针对 Java 程序的优化方法有重要的现实意义。本书 以 Java 性能调优为主线,系统地阐述了与 Java 性能优化相关的知识与技巧。 本书共 6 章,先后从软件设计、软件编码、JVM 调优以及程序故障排除等方面介绍针对 Java 程序的 优化方法。第 1 章介绍性能的基本概念、定律、系统调优的过程和注意事项;第 2 章从设计层面介绍与 性能相关的设计模式、组件。第 3 章从代码层面介绍如何编写高性能的 Java 程序;第 4 章介绍并行开发 和如何通过多线程提高系统性能;第 5 章立足于 JVM 虚拟机层面,介绍如何通过设置合理的 JVM 参数 提升 Java 程序的性能;第 6 章为工具篇,介绍获取和监控程序或系统性能指标的各种工具,包括相关的 故障排查工具。 本书适合所有 Java 程序员、软件设计师、架构师以及软件开发爱好者,对于有一定经验的 Java 工程 师,本书更能帮助突破技术瓶颈,深入 Java 内核开发! 本书封面贴有清华大学出版社防伪标签,无标签者不得销售。 版权所有,侵权必究。侵权举报电话:010-62782989 13701121933 图书在版编目(CIP)数据 Java 程序性能优化:让你的 Java 程序更快、更稳定 / 葛一鸣等编著. —北京:清华大学出版社,2012.10 ISBN 978-7-302-29625-6 Ⅰ. ①J… Ⅱ. ①葛… Ⅲ. ①JAVA 语言 – 程序设计 Ⅳ. ①TP312 中国版本图书馆 CIP 数据核字(2012)第 184193 号 责任编辑:夏兆彦 封面设计:欧振旭 责任校对:徐俊伟 责任印制: 出版发行:清华大学出版社 网 址:http://www.tup.com.cn, http://www.wqbook.com 地 址:北京清华大学学研大厦 A 座 邮 编:100084 社 总 机:010-62770175 邮 购:010-62786544 投稿与读者服务:010-62776969,c-service@tup.tsinghua.edu.cn 质量反馈:010-62772015,zhiliang@tup.tsinghua.edu.cn 印 刷 者: 装 订 者:肖 米 经 销:全国新华书店 开 本:185mm×260mm 印 张:26 字 数:649 千字 版 次:2012 年 10 月第 1 版 印 次:2012 年 10 月第 1 次印刷 印 数:1~5000 定 价:25.00 元 产品编号:047877-01 前 言 关于 Java Java 是目前应用最为广泛的软件开发平台之一。随着 Java 以及 Java 社区的不断壮大, Java 早已不再是简简单单的一门计算机语言了,它更是一个平台、一种文化、一个社区。 作为一个平台,JVM 虚拟机扮演着举足轻重的作用。除了 Java 语言,任何一种能够被 编译成字节码的计算机语言都属于 Java 这个平台。Groovy、Scala、JRuby 等都是 Java 平台的 一个部分,它们依赖于 JVM 虚拟机,同时,Java 平台也因为它们变得更加丰富多彩。 作为一种文化,Java 几乎成为了“开源”的代名词。在 Java 程序中,有着数不清的开 源软件和框架,如 Tomcat、Struts、Hibernate、Spring 等。就连 JDK 和 JVM 自身也有不少开 源的实现,如 OpenJDK、Harmony。可以说,“共享”的精神在 Java 世界里体现得淋漓尽致。 作为一个社区,Java 拥有无数的开发人员,数不清的论坛、资料。从桌面应用软件、嵌 入式开发到企业级应用、后台服务器、中间件,都可以看到 Java 的身影。其应用形式之复 杂、参与人数之庞大也令人咋舌。可以说,Java 社区已经俨然成为了一个良好而庞大的生态 系统。 此外,纯粹作为一门软件开发语言,Java 非常容易学习,其学习曲线较 C++等老牌计算 机语言相比,也比较平缓。因为它尽力简化或去除了 C++中许多晦涩、多余和难以理解的部 分,如指针、虚函数、多继承等。 本书架构 本书主要介绍 Java 应用程序的优化方法和技巧,总共分为 6 章。 第 1 章是综述,介绍了性能的基本概念、两个重要的定律(木桶原理和 Amdahl 定律), 以及系统调优的一般过程与注意事项。 第 2 章从设计层面,介绍与性能相关的设计模式、组件以及有助于改善性能的软件设计 思想。 第 3 章从代码层面介绍如何编写高性能的 Java 代码。涉及的主要内容有字符串的优化 处理、文件 I/O 的优化、核心数据库结构的使用、Java 的引用类型以及一些常用的惯例。 第 4 章介绍并行程序开发的相关内容,以及如何通过多线程提高系统性能。先后介绍了 并发设计模式、线程池、并发数据结构的使用、并发控制方法、“锁”的优化、无锁的使用 以及协程。 第 5 章立足于 JVM 虚拟机层面,介绍如何通过设置合理的 JVM 参数提升 Java 程序的性 能。 第 6 章为工具篇,主要介绍获取和监控程序或系统性能指标的各种工具,以及 Java 应 用程序相关的故障排查工具。 本书特点 本书的主要特点有: * 专注于介绍 Java 应用程序的优化方法、技巧和思想,并深度剖析 JDK 的部分实现。 * 具有较强的层次性和连贯性,依次介绍了在软件设计层面、代码层面、JVM 虚拟机层面的 优化方法。 * 理论结合实际,使用丰富的示例帮助读者理解理论知识。 阅读人群 要通读本书并取得良好的学习效果,要求读者具备 Java 的基本知识。本书不是一本帮 助初学者入门的书籍。因此,本书适合以下读者: * 拥有一定开发经验的 Java 开发人员; * Java 软件设计师、架构师; * 系统调优人员; * 有一定的 Java 基础并希望更进一步的程序员。 本书的约定 本书在叙述过程中,有如下约定: * 本书中所述的 JDK 1.5、JDK 1.6 分别等同于 JDK 5、JDK 6; * 如无特殊说明,JVM 虚拟机均指 Hot Spot 虚拟机; * 如无特殊说明,本书的程序、示例均在 JDK 1.6 环境中运行。 下载提示 本书涉及的源程序请读者直接登录清华大学出版社网站(http://www.tup.com.cn),搜 索到本书页面后按照提示进行下载。 本书作者 本书由葛一鸣编著。其他参与编写和资料整理的人员有武冬、郅晓娜、孙美芹、卫丽行、 尹翠翠、蔡继文、陈晓宇、迟剑、邓薇、郭利魁、金贞姬、李敬才、李萍、刘敬、陈慧、刘 艳飞、吕博、全哲、佘勇、宋学江、王浩、王康、王楠、杨宗芳、张严虎、周玉。 本书的写作过程远比我想象中的艰辛。为了让全书能够更清楚、更正确地表达和论述, 笔者经历了无数个不眠之夜。即使现在回想起来,也忍不住让我打个寒战。但由于写作水平 和写作时间的限制,书中难免会有不妥之处。为此,读者可以通过邮箱 bookservice2008@ 163.com 与笔者联系。 致谢 在本书的写作过程中,我充满着感激之情。首先是对我的家人,在本书完稿前,父亲病 重,但我由于工作上的繁忙未能抽出太多时间照顾他,幸好得到了母亲的大力支持和父亲的 谅解,我才能够鼓足勇气,全身心投入到写作之中。同时,母亲对我的悉心照料也让我能够 更加专注到工作之中。 同时,我要感谢我的工作单位 UT 斯达康以及两位前辈 Rex Zhu 和 Tao Tao。正是他们在 平时工作中对我的细心指导,才能让我有所进步和积累。而这一切,正是本书的基础。 最后,再次感谢我的母亲,祝她身体健康。 葛一鸣 ?? ?? ?? ?? Java 程序性能优化——让你的 Java 程序更快、更稳定 前 言 ·II· ·III· 目 录 第 1 章 Java 性能调优概述 1 1.1 性能概述 1 1.1.1 看懂程序的性能 1 1.1.2 性能的参考指标 2 1.1.3 木桶原理与性能瓶颈 2 1.1.4 Amdahl 定律 3 1.2 性能调优的层次 5 1.2.1 设计调优 5 1.2.2 代码调优 5 1.2.3 JVM 调优 6 1.2.4 数据库调优 6 1.2.5 操作系统调优 6 1.3 基本调优策略和手段 7 1.3.1 优化的一般步骤 7 1.3.2 系统优化注意事项 8 1.4 小结 8 第 2 章 设计优化 10 2.1 善用设计模式 10 2.1.1 单例模式 10 2.1.2 代理模式 15 2.1.3 享元模式 24 2.1.4 装饰者模式 27 2.1.5 观察者模式 33 2.1.6 Value Object 模式 37 2.1.7 业务代理模式 40 2.2 常用优化组件和方法 43 2.2.1 缓冲(Buffer) 43 2.2.2 缓存(Cache) 46 2.2.3 对象复用——“池” 50 2.2.4 并行替代串行 56 2.2.5 负载均衡 56 2.2.6 时间换空间 62 2.2.7 空间换时间 63 2.3 小结 65 第 3 章 Java 程序优化 66 3.1 字符串优化处理 66 3.1.1 String 对象及其特点 66 3.1.2 subString()方法的内存泄漏 68 3.1.3 字符串分割和查找 71 3.1.4 StringBuffer 和 StringBuilder 74 3.2 核心数据结构 79 3.2.1 List 接口 79 3.2.2 Map 接口 86 3.2.3 Set 接口 97 3.2.4 优化集合访问代码 99 3.2.5 RandomAccess 接口 101 3.3 使用 NIO 提升性能 102 3.3.1 NIO 的 Buffer 类族和 Channel 103 3.3.2 Buffer 的基本原理 104 3.3.3 Buffer 的相关操作 107 3.3.4 MappedByteBuffer 性能评估 114 3.3.5 直接内存访问 116 3.4 引用类型 118 3.4.1 强引用 119 3.4.2 软引用 120 3.4.3 弱引用 121 3.4.4 虚引用 122 3.4.5 WeakHashMap 类及其实现 125 3.5 有助于改善性能的技巧 127 3.5.1 慎用异常 127 3.5.2 使用局部变量 128 3.5.3 位运算代替乘除法 128 3.5.4 替换 switch 129 3.5.5 一维数组代替二维数组 130 3.5.6 提取表达式 131 3.5.7 展开循环 132 3.5.8 布尔运算代替位运算 133 3.5.9 使用 arrayCopy () 134 3.5.10 使用 Buffer 进行 I/O 操作 135 3.5.11 使用 clone()代替 new 137 3.5.12 静态方法替代实例方法 139 3.6 小结 140 第 4 章 并行程序开发及优化 141 4.1 并行程序设计模式 141 4.1.1 Future 模式 141 4.1.2 Master-Worker 模式 148 4.1.3 Guarded Suspension 模式 153 4.1.4 不变模式 160 4.1.5 生产者-消费者模式 162 4.2 JDK 多任务执行框架 166 4.2.1 无限制线程的缺陷 166 4.2.2 简单的线程池实现 167 4.2.3 Executor 框架 171 4.2.4 自定义线程池 173 4.2.5 优化线程池大小 177 4.2.6 扩展 ThreadPoolExecutor 178 4.3 JDK 并发数据结构 179 4.3.1 并发 List 179 4.3.2 并发 Set 182 4.3.3 并发 Map 182 4.3.4 并发 Queue183 4.3.5 并发 Deque 186 4.4 并发控制方法 187 4.4.1 Java 内存模型与 volatile 187 4.4.2 同步关键字 synchronized 190 4.4.3 ReentrantLock 重入锁 192 4.4.4 ReadWriteLock 读写锁 195 4.4.5 Condition 对象 196 4.4.6 Semaphore 信号量 198 4.4.7 ThreadLocal 线程局部变量 200 4.5 “锁”的性能和优化 201 4.5.1 线程的开销 201 4.5.2 避免死锁 202 4.5.3 减小锁持有时间 206 4.5.4 减小锁粒度 207 4.5.5 读写分离锁来替换独占锁 208 4.5.6 锁分离 209 4.5.7 重入锁(ReentrantLock)和内部锁(synchronized) 210 4.5.8 锁粗化(Lock Coarsening) 211 4.5.9 自旋锁(Spinning Lock) 212 4.5.10 锁消除(Lock Elimination) 212 4.5.11 锁偏向(Biased Lock) 214 4.6 无锁的并行计算 214 4.6.1 非阻塞的同步/无锁 214 4.6.2 原子操作 215 4.6.3 Amino 框架介绍 217 4.6.4 Amino 集合 218 4.6.5 Amino 树 222 4.6.6 Amino 图 222 4.6.7 Amino 简单调度模式 223 4.7 协程 226 4.7.1 协程的概念 226 4.7.2 Kilim 框架简介 226 4.7.3 Task 及其状态 227 4.7.4 Fiber 及其状态 228 4.7.5 Kilim 开发环境配置 228 4.7.6 Kilim 之 Hello World 230 4.7.7 多任务通信 232 4.7.8 Kilim 实例及性能评估 233 4.8 小结 236 第 5 章 JVM 调优237 5.1 Java 虚拟机内存模型 237 5.1.1 程序计数器 237 5.1.2 Java 虚拟机栈 238 5.1.3 本地方法栈 243 5.1.4 Java 堆 244 5.1.5 方法区 245 5.2 JVM 内存分配参数 249 5.2.1 设置最大堆内存 249 5.2.2 设置最小堆内存 250 5.2.3 设置新生代 251 5.2.4 设置持久代 252 5.2.5 设置线程桟 253 5.2.6 堆的比例分配 254 5.2.7 堆分配参数总结 256 5.3 垃圾收集基础 257 5.3.1 垃圾收集的作用 257 5.3.2 垃圾回收算法与思想 258 5.3.3 垃圾收集器的类型 262 5.3.4 评价 GC 策略的指标 263 5.3.5 新生代串行收集器 264 5.3.6 老年代串行收集器 265 5.3.7 并行收集器 265 5.3.8 新生代并行回收(Parallel Scavenge)收集器 266 5.3.9 老年代并行回收收集器 267 5.3.10 CMS 收集器 267 5.3.11 G1 收集器(Garbage First) 270 5.3.12 Stop the World 案例 270 5.3.13 收集器对系统性能的影响 272 5.3.14 GC 相关参数总结 273 5.4 常用调优案例和方法 275 5.4.1 将新对象预留在新生代 275 5.4.2 大对象进入老年代 278 5.4.3 设置对象进入老年代的年龄 279 5.4.4 稳定与震荡的堆大小 280 5.4.5 吞吐量优先案例 281 5.4.6 使用大页案例 282 5.4.7 降低停顿案例 282 5.5 实用 JVM 参数 283 5.5.1 JIT 编译参数 283 5.5.2 堆快照(堆 Dump) 284 5.5.3 错误处理 285 5.5.4 取得 GC 信息 285 5.5.5 类和对象跟踪 287 5.5.6 控制 GC 288 5.5.7 选择类校验器 289 5.5.8 Solaris 下线程控制 289 5.5.9 使用大页 289 5.5.10 压缩指针 289 5.6 实战 JVM 调优 290 5.6.1 Tomcat 简介与启动加速 290 5.6.2 Web 应用程序介绍 292 5.6.3 JMeter 介绍与使用 293 5.6.4 调优前 Web 应用运行状况 296 5.6.5 调优过程 297 5.7 总结 298 第 6 章 Java 性能调优工具 299 6.1 Linux 命令行工具 299 6.1.1 top 命令 299 6.1.2 sar 命令 301 6.1.3 vmstat 命令 302 6.1.4 iostat 命令 304 6.1.5 pidstat 工具 305 6.2 Windows 工具 309 6.2.1 任务管理器 309 6.2.2 perfmon 性能监控工具 311 6.2.3 Process Explorer 313 6.2.4 pslist 命令行 315 6.3 JDK 命令行工具 317 6.3.1 jps 命令 317 6.3.2 jstat 命令 318 6.3.3 jinfo 命令 322 6.3.4 jmap 命令 323 6.3.5 jhat 命令 324 6.3.6 jstack 命令 326 6.3.7 jstatd 命令 329 6.3.8 hprof 工具 330 6.4 JConsole 工具 332 6.4.1 JConsole 连接 Java 程序 332 6.4.2 Java 程序概况 333 6.4.3 内存监控 333 6.4.4 线程监控 335 6.4.5 类加载情况 335 6.4.6 虚拟机信息 336 6.4.7 MBean 管理 337 6.4.8 使用插件 338 6.5 Visual VM 多合一工具 339 6.5.1 Visual VM 连接应用程序 339 6.5.2 监控应用程序概况 342 6.5.3 Thread Dump 和分析 343 6.5.4 性能分析 344 6.5.5 快照 346 6.5.6 内存快照分析 347 6.5.7 MBean 管理 349 6.5.8 TDA 使用 349 6.5.9 BTrace 介绍 350 6.6 Visual VM 对 OQL 的支持 356 6.6.1 Visual VM 的 OQL 基本语法 356 6.6.2 内置 heap 对象 357 6.6.3 对象函数 359 6.6.4 集合/统计函数 362 6.6.5 程序化 OQL 366 6.7 MAT 内存分析工具 368 6.7.1 初识 MAT 368 6.7.2 浅堆和深堆 371 6.7.3 支配树(Dominator Tree) 374 6.7.4 垃圾回收根 375 6.7.5 内存泄露检测 376 6.7.6 最大对象报告 378 6.7.7 查找支配者 378 6.7.8 线程分析 379 6.7.9 集合使用情况分析 380 6.7.10 扩展 MAT 381 6.8 MAT 对 OQL 的支持 385 6.8.1 Select 子句 385 6.8.2 From 子句 387 6.8.3 Where 子句 389 6.8.4 内置对象与方法 389 6.9 JProfile 简介 393 6.9.1 JProfile 使用配置 393 6.9.2 内存视图 394 6.9.3 堆快照 394 6.9.4 CPU 视图 395 6.9.5 线程视图 397 6.9.6 JVM 统计信息 397 6.9.7 触发器 398 6.10 小结 400 ?? ?? ?? ?? Java 程序性能优化——让你的 Java 程序更快、更稳定 目 录 ·X· ·XI· 第 1 章 Java 性能调优概述 本章对性能优化技术进行整体性的介绍,让读者了解性能的概念和性能优化的基本思 路与方法。掌握这些内容,有助于读者对性能问题进行系统性的分析。 本章涉及的主要知识点有:  评价性能的主要指标;  木桶原理的概念及其在性能优化中的应用;  Amdahl 定律的含义;  性能调优的层次;  系统优化的一般步骤和注意事项。 1.1 性 能 概 述 为什么程序总是那么慢?它现在到底在干什么?时间都花到哪里去了?也许,你经常 会抱怨这些问题。如果是这样,那说明你的程序出了性能问题。和功能性问题相比,性能 问题在有些情况下,可能并不算什么太大的问题,将就将就,也就过去了。但是,严重的 性能问题会导致程序瘫痪、假死,直至奔溃。本节就先来认识性能的各种表现和指标。 1.1.1 看懂程序的性能 对客户端程序而言,拙劣的性能会严重影响用户体验。界面停顿、抖动、响应迟钝等 问题会遭到用户不停的抱怨。一个典型的例子就是 Eclipse IDE 工具在 Full GC 时会出现程 序假死现象,相信一定被不少开发人员所诟病。对于服务器程序来说,性能问题则更为重 要,相信不少后台服务器软件都有各自的性能目标。以 Web 服务器为例,服务器的响应时 间、吞吐量就是两个重要的性能参数。当服务器承受巨大的访问压力时,可能出现响应时 间变长、吞吐量下降,甚至是抛出内存溢出异常而崩溃。这些问题,都是性能调优需要解 决的。 一般来说,程序的性能通过以下几个方面来表现:  执行速度:程序的反映是否迅速,响应时间是否足够短。  内存分配:内存分配是否合理,是否过多地消耗内存或者存在泄漏。  启动时间:程序从运行到可以正常处理业务需要花费多长时间。  负载承受能力:当系统压力上升时,系统的执行速度、响应时间的上升曲线是否 平缓。 Java 程序性能优化——让你的 Java 程序更快、更稳定 ·2· 1.1.2 性能的参考指标 为了能够科学地进行性能分析,对性能指标进行定量评测是非常重要的。目前,一些 可以用于定量评测的性能指标有:  执行时间:一段代码从开始运行到运行结束,所使用的时间。  CPU 时间:函数或者线程占用 CPU 的时间。  内存分配:程序在运行时占用的内存空间。  磁盘吞吐量:描述 I/O 的使用情况。  网络吞吐量:描述网络的使用情况。  响应时间:系统对某用户行为或者事件做出响应的时间。响应时间越短,性能 越好。 1.1.3 木桶原理与性能瓶颈 木桶原理又称“短板理论”,其核心思想是:一只木桶盛水的多少,并不取决于桶壁 上最高的那块木块,而是取决于桶壁上最短的那块,如图 1.1 所示。 图 1.1 木桶原理示意图 将这个理论应用到系统性能优化上,可以这么理解,即使系统拥有充足的内存资源和 CPU 资源,但是如果磁盘 I/O 性能低下,那么系统的总体性能是取决于当前最慢的磁盘 I/O 速度,而不是当前最优越的 CPU 或者内存。在这种情况下,如果需要进一步提升系统性能, 优化内存或者 CPU 资源是毫无用处的。只有提高磁盘 I/O 性能才能对系统的整体性能进行 优化。而此时,磁盘 I/O 就是系统的性能瓶颈。 注意:根据木桶原理,系统的最终性能取决于系统中性能表现最差的组件。因此,为了 提升系统整体性能,必须对系统中表现最差的组件进行优化,而不是对系统中表 现良好的组件进行优化。 第 1 章 Java 性能调优概述 ·3· 根据应用的特点不同,任何计算机资源都有可能成为系统瓶颈。其中,最有可能成为 系统瓶颈的计算资源如下。  磁盘 I/O:由于磁盘 I/O 读写的速度要比内存慢很多,程序在运行过程中,如果需 要等待磁盘 I/O 完成,那么低效的 I/O 操作会拖累整个系统。  网络操作:对网络数据进行读写的情况与磁盘 I/O 类似。由于网络环境的不确定性, 尤其是对互联网上数据的读写,网络操作的速度可能比本地磁盘 I/O 更慢。因此, 如不加特殊处理,也极可能成为系统瓶颈。  CPU:对计算资源要求较高的应用,由于其长时间、不间断地大量占用 CPU 资源, 那么对 CPU 的争夺将导致性能问题。如科学计算、3D 渲染等对 CPU 需求旺盛的 应用。  异常:对 Java 应用来说,异常的捕获和处理是非常消耗资源的。如果程序高频率 地进行异常处理,则整体性能便会有明显下降。  数据库:大部分应用程序都离不开数据库,而海量数据的读写操作可能是相当费 时的。而应用程序可能需要等待数据库操作完成或者返回请求的结果集,那么缓 慢的同步操作将成为系统瓶颈。  锁竞争:对高并发程序来说,如果存在激烈的锁竞争,无疑是对性能极大的打击。 锁竞争将会明显增加线程上下文切换的开销。而且,这些开销都是与应用需求无 关的系统开销,白白占用宝贵的 CPU 资源,却不带来任何好处。  内存:一般来说,只要应用程序设计合理,内存在读写速度上不太可能成为性能 瓶颈。除非应用程序进行了高频率的内存交换和扫描,但这些情况比较少见。使 内存制约系统性能的最可能的情况是内存大小不足。与磁盘相比,内存的大小似 乎小的可怜,这意味着应用软件只能尽可能将常用的核心数据读入内存,这在一 定程度上降低了系统性能。 1.1.4 Amdahl 定律 Amdahl 定律是计算机科学中非常重要的定律,它定义了串行系统并行化后加速比的计 算公式和理论上限。 加速比定义:加速比=优化前系统耗时/优化后系统耗时 所谓加速比,就是优化前的耗时与优化后耗时的比值。加速比越高,表明优化效果越 明显。 Amdahl 定律给出了加速比与系统并行度和处理器数量的关系。设加速比为 Speedup, 系统内必须串行化的程序比重为 F,CPU 处理器数量为 N,则有: 1 1+ Speedup FFN   根据这个公式,如果 CPU 处理器数量趋于无穷,那么加速比与系统的串行化率成反比, 如果系统中必须有 50%的代码串行执行,那么系统的最大加速比为 2。 Java 程序性能优化——让你的 Java 程序更快、更稳定 ·4· 假设有一程序分为以下步骤执行,每个执行步骤花费 100 个时间单位。其中,只有步 骤 2 和步骤 5 可以进行并行,步骤 1、3、4 必须串行,如图 1.2 所示。在全串行的情况下, 系统合计耗时 500 个时间单位。 图 1.2 串行工作流程 若将步骤 2 和步骤 5 并行化,假设在双核处理上,则有如图 1.3 所示的处理流程。在 这种情况下,步骤 2 和步骤 5 的耗时将为 50 个是单位。故系统整体耗时为 400 个时间单位。 根据加速比的定义有: 加速比=优化前系统耗时/优化后系统耗时=500/400=1.25 或者前文中给出的加速比公式。由于 5 个步骤中,3 个步骤必须串行,因此其串行化 比重为 3/5=0.6,即 F=0.6,且双核处理器的处理器个数 N 为 2。代入公式得: 加速比=1/(0.6+(1-0.6)/2)=1.25 图 1.3 双核处理上的并行化 在极端情况下,假设并行处理器个数为无穷大,则有如图 1.4 所示的处理过程。步骤 2 和步骤 5 的处理时间趋于 0。即 使这样,系统整体耗时依然大于 300 个时间单位。即加速 比的极限为 500/300=1.67。 图 1.4 极端情况下的并行化 使用加速比计算公式,N 趋于无穷大,有 Speedup=1/F,且 F=0.6,故有 Speedup=1.67。 由此可见,为了提高系统的速度,仅增加 CPU 处理器的数量并不一定能起到有效的作 用,需要从根本上修改程序的串行行为,提高系统内可并行化的模块比重,在此基础上, 合理增加并行处理器数量,才能以最小的投入,得到最大的加速比。 注意:根据 Amdahl 定律,使用多核 CPU 对系统进行优化,优化的效果取决于 CPU 的 数量以及系统中的串行化程序的比重。CPU 数量越多,串行化比重越低,则优化 效果越好。仅提高 CPU 数量而不降低程序的串行化比重,也无法提高系统性能。 第 1 章 Java 性能调优概述 ·5· 1.2 性能调优的层次 为了提升系统性能,开发人员可以从系统的各个角度和层次对系统进行优化。除了最 常见的代码优化外,在软件架构上、JVM 虚拟机层、数据库以及操作系统层面都可以通过 各种手段进行调优,从而在整体上提升系统的性能。 1.2.1 设计调优 设计调优处于所有调优手段的上层,它往往需要在软件开发之前进行。在软件开发之 初,软件架构师就应该评估系统可能存在的各种潜在问题,并给出合理的设计方案。由于 软件设计和架构对软件整体质量有决定性的影响,所以,设计调优对系统性能的影响也是 最大的。如果说,代码优化、JVM 优化都是对系统微观层面上“量”的优化,那么设计优 化就是对系统在宏观层面上“质”的优化。 设计优化的一大显著特点是,它可以规避某一个组件的性能问题,而非改良该组件的 实现。比如,系统中组件 A 需要等待某事件 E 才能触发一个行为。如果组件 A 通过循环 监控不断监测事件 E 是否发生,其监测行为必然会占用部分系统资源,因此,开发人员必 须在监测频率和资源消耗间取得平衡。如果监测频率太低,虽然减少了资源消耗,但是系 统实时反应性就会降低。如果进行代码层的调优,就需要优化监测方法的实现以及求得一 个最为恰当的监测频率。 而若将此问题预留在设计层解决,便可以使用事件通知的方式将系统行为进行倒置。如 使用第 2 章中提到的观察者模式,在事件 E 发生的时刻,由事件 E 通知组件 A,从而触发组 件 A 的行为。这种设计方法弃用了存在性能隐患的循环监控,从根本上解决了这一问题。 从某种程度上说,设计优化直接决定了系统的整体品质。如果在设计层考虑不周,留 下太多问题隐患,那么这些“质”上的问题,也许无法再通过代码层的优化进行弥补。因 此,开发人员必须在软件设计之初,认真仔细考虑软件系统的性能问题。 进行设计优化时,设计人员必须熟悉常用的软件设计方法、设计模式、基本性能组件 和常用优化思想,并将其有机地集成在软件系统中。 注意:一个良好的系统设计可以规避很多潜在的性能问题。因此,尽可能多花些时间在 系统设计上,是创建高性能程序的关键。 1.2.2 代码调优 代码调优是在软件开发过程中,或者在软件开发完成后,软件维护过程中进行的对程 序代码的改进和优化。代码优化涉及诸多编码技巧,需要开发人员熟悉相关语言的 API, 并在合适的场景中正确使用相关 API 或类库。同时,对算法、数据结构的灵活使用,也是 代码优化的重要内容。 Java 程序性能优化——让你的 Java 程序更快、更稳定 ·6· 虽然代码优化是从微观上对性能进行调整,但是一个“好”的实现和一个“坏”的实 现对系统的影响也是非常大的。比如,同样作为 List 的实现,LinkedList 和 ArrayList 在随 机访问上的性能却可以相差几个数量级;又如,同样是文件读写的实现,使用 Stream 方式 与 Java NIO 的方式,其性能可能又会相差一个数量级。 因此,虽然与设计优化相比,笔者将代码优化称为在微观层面上的优化,但是它却是 对系统性能产生最直接影响的优化方法。 1.2.3 JVM 调优 由于 Java 软件总是运行在 JVM 虚拟机之上,对 JVM 虚拟机进行优化也能在一定程度 上提升 Java 程序的性能。JVM 调优通常可以在软件开发后期进行,如在软件开发完成, 或者在软件开发的某一里程碑阶段。 作为 Java 软件的运行平台,JVM 的各项参数将会直接影响 Java 程序的性能。比如, JVM 的堆大小、垃圾回收策略等。 要进行 JVM 层面的调优,需要开发人员对 JVM 的运行原理和基本内存结构有一定了 解。如,堆内存的结构、GC 的种类等。然后,依据应用程序的特点,设置合理的 JVM 启 动参数。 1.2.4 数据库调优 对绝大部分应用系统而言,数据库是必不可少的一部分。Java 程序可以使用 JDBC 的 方式连接数据库。对数据库的调优可以分为 3 个部分:  在应用层对 SQL 语句进行优化;  对数据库进行优化;  对数据库软件进行优化。 在应用层优化数据库访问,涉及大量的编程技巧。比如,当使用 JDBC 进行查询时, 对于大量的拥有相同结构的 SQL 查询,可以使用 PreparedStatement 代替 Statement,以提 高数据库的查询效率;在 Select 语句中,显示指定要查询的列名,避免使用星号“*”。 在对数据库进行优化时,主要目的是建立一个具有良好表结构的数据库。比如,为了 提高多表级联查询效率,可以合理地使用冗余字段;对于大表,可以使用行的水平切割或 者类似 Oracle 分区表的技术;为了提高数据库查询效率,可以建立有效且合理的索引。 对于数据库软件的优化,根据不同的数据库,如 Oracle、MySQL 或者 SQL Server 都 拥有不同的方式。以 Oracle 为例,设置合理大小的共享池、缓存缓冲区或者 PGA,对 Oracle 的运行性能都有很大的影响。 鉴于本书的讨论范围,数据库优化将不作为本书的阐述重点。 1.2.5 操作系统调优 作为软件运行的基础平台,操作系统的性能对应用系统也有较大的影响。不同类型的 第 1 章 Java 性能调优概述 ·7· 操作系统,调优的手段和参数可能会有所不同。比如,在主流 UNIX 系统中,共享内存段、 信号量、共享内存最大值(shmmax)、共享内存最小值(shmmin)等都是可以进行优化 的系统资源。此外,如最大文件句柄数、虚拟内存大小、磁盘的块大小等参数都可能对软 件的性能产生影响。图 1.5 展示了在 Windows 平台上,配置虚拟内存的界面。 图 1.5 Windows 下设置虚拟内存 说明:操作系统的性能调优不在本书的讨论范围内,有兴趣的读者可以参考相关书籍。 1.3 基本调优策略和手段 存在性能问题的系统,十之八九是由某一系统瓶颈导致的。只要找到该性能瓶颈,分 析瓶颈的形成原因,对症下药,使用合理的方法解决系统瓶颈,就能从根本上提升性能。 所以,系统性能优化的最主要目的就是查找并解决性能瓶颈问题。但同时值得注意的是, 性能优化往往会涉及对原有的实现进行较大的修改,因此,很难保证这些修改不引入新的 问题。所以,在性能优化前,需要对性能优化的目标、方法进行统筹的安排。 1.3.1 优化的一般步骤 对软件系统进行优化,首先需要有明确的性能目标,清楚地指出优化的对象和最终目 的。其次,需要在目标平台上对软件进行测试,通过各种性能监控和统计工具,观测和确 认当前系统是否已经达到相关目标,若已经达到,则没有必要再进行优化;若当前系统性 能尚未达到优化目标,则需要查找当前的性能瓶颈。 可能成为性能瓶颈的因素有很多,比如:磁盘 I/O、网络 I/O 和 CPU。当找到性能瓶 Java 程序性能优化——让你的 Java 程序更快、更稳定 ·8· 颈后,首先需要定位相关代码,确认是否在软件实现上存在问题或者优化空间。若有,则 进行代码优化;若已经没有代码优化空间,则需要考虑进行 JVM 层、数据库层或者操作系 统的优化。甚至,可以考虑修改原有设计,或者提升硬件性能。 当优化完成后,需要在目标平台上进行确认测试。若达到性能目标,则优化过程结束; 若没有,则需要再次查找系统瓶颈,以此反复,如图 1.6 所示。 图 1.6 性能优化的一般步骤 1.3.2 系统优化注意事项 软件的性能优化虽然能提升软件的性能,但是优化过程往往伴随着一些风险和弊端。 比如,为了优化某一段代码的实现,就需要重写原有的算法,而这就很可能引入新的 Bug。 重新实现新的功能模块也同时意味着需要重新对其进行完整的功能性测试,使优化前所做 的测试工作变得毫无意义。而且,优化后的代码与优化前的代码相比,可能会比较晦涩难 懂,从一定程度上影响了系统的可维护性。因此,软件优化需要在软件功能、正确性和可 维护性间取得平衡,而不应该过分地追求软件性能。 在进行优化前,必须要有明确的已知问题和性能目标,决不可为了“优化”而“优化”。 在动手前,必须知道自己要干什么。任何优化都是为了解决具体的软件问题,如果软件已 经可以正常工作,在性能问题没有暴露前,只是凭着主观臆断对某些模块进行性能改进, 从软件规范化开发的角度上来说,是非常冒险的。因为修改后的新代码没有经过完整的测 试,软件质量就没有保障。而且,优化后的性能提升幅度可能也不足以让开发者如此费尽 心机。因此,在进行软件优化时,必须要进行慎重的评估。 注意:性能调优必须有明确的目标。不要为了调优而调优,如果当前程序并没有明显的 性能问题,盲目地进行调整,其风险可能远远大于收益。 1.4 小 结 通过本章的学习,读者应该了解性能的基本概念及其常用的参考指标。此外,本章还 第 1 章 Java 性能调优概述 ·9· 较为详细地介绍了与性能调优相关的两个重要理论——木桶原理以及 Amdahl 定律。 根据木桶原理,系统的最终性能总是由系统中性能最差的组件决定的。因此,改善该 组件的性能对提升系统整体性能有重要的作用。而根据 Amdahl 定律,可以知道只是增加 处理器数量对提升系统性能并没有太大实际意义,必须同时提高程序的并行化比重。 本章还简要介绍了在软件开发和维护过程中可以进行性能优化的各个阶段。比如,在 软件的设计阶段,就需要选用合理的软件结构和性能组件;在编码阶段,需要提高代码的 执行效率;对于 Java 应用程序,在系统的运行期,还需要设置合理的 JVM 虚拟机参数; 同时,优化数据库和操作系统也对系统整体性能有直接影响。 在本章最后,简要介绍了性能优化的一般步骤和注意事项。
还剩21页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 3 金币 [ 分享pdf获得金币 ] 2 人已下载

下载pdf

pdf贡献者

672381992

贡献于2014-08-28

下载需要 3 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf