新書推薦:
《
失衡与重塑——百年变局下的中国与世界经济
》
售價:HK$
132.2
《
南方谈话:邓小平在1992
》
售價:HK$
80.6
《
纷纭万端 : 近代中国的思想与社会
》
售價:HK$
109.8
《
中国古代文体形态研究(第四版)(中华当代学术著作辑要)
》
售價:HK$
168.0
《
朋党之争与北宋政治·大学问
》
售價:HK$
99.7
《
甲骨文丛书·波斯的中古时代(1040-1797年)
》
售價:HK$
88.5
《
以爱为名的支配
》
售價:HK$
62.7
《
台风天(大吴作品,每一种生活都有被看见的意义)
》
售價:HK$
53.8
|
編輯推薦: |
1、系统介绍系统调优的解决思路和技术实现2、结合大家最为熟知的12306、电商等案例3、架构、设计、开发、算法等多层次多角度思路和策略4、涉及内存、IO等各种问题,提供丰富的经验参考5、语言通俗易懂,引人入胜
|
內容簡介: |
本书主要提供Java性能调优方面的参考建议及经验交流。作者力求做到知识的综合传播,而不是仅仅只针对Java虚拟机调优进行讲解,另外力求每一章节都有实际的案例支撑。具体包括:性能优化策略、程序编写及硬件服务器的基础知识、Java API优化建议、算法类程序的优化建议、并行计算优化建议、Java程序性能监控及检测、JVM原理知识、其他相关优化知识等。
通读本书后,读者可以深入了解Java性能调优的许多主题及相关的综合性知识。读者也可以把本书作为参考,对于感兴趣的主题,直接跳到相应章节寻找答案。
总的来说,性能调优在很大程度上是一门艺术,解决的Java性能问题越多,技艺才会越精湛。我们不仅要关心JVM的持续演进,也要积极地去了解底层的硬件平台和操作系统的进步。
|
關於作者: |
12年投资银行项目、分布式计算项目工作经验,IBM开发者论坛专家作者。
一名IT技术狂热爱好者,一名顽强到底的工程师。我推崇技术创新、思维创新,对于新技术非常的热爱,致力于技术研发、研究,通过发布文章、书籍、互动活动的形式积极推广软件技术。
欢迎添加作者微信“michael_tec”,共同探讨IT技术话题。
|
目錄:
|
第1章 性能调优策略概述1
1.1 为什么需要调优1
1.2 性能优化的参考因素5
1.2.1 传统计算机体系的分歧5
1.2.2 导致系统瓶颈的计算资源7
1.2.3 程序性能衡量指标8
1.2.4 性能优化目标9
1.2.5 性能优化策略10
1.3 性能调优分类方法11
1.3.1 业务方面12
1.3.2 基础技术方面12
1.3.3 组件方面17
1.3.4 架构方面19
1.3.5 层次方面20
1.4 本章小结21
第2章 优化前的准备知识22
2.1 服务器知识23
2.1.1 内存23
2.1.2 GPUCPU44
2.1.3 硬盘49
2.1.4 网络架构51
2.2 新兴技术53
第3章 Java API调用优化建议54
3.1 面向对象及基础类型55
3.1.1 采用Clone方式创建对象55
3.1.2 避免对boolean判断55
3.1.3 多用条件操作符56
3.1.4 静态方法代替实例方法56
3.1.5 有条件地使用final关键字58
3.1.6 避免不需要的instanceof操作58
3.1.7 避免子类中存在父类转换59
3.1.8 建议多使用局部变量60
3.1.9 运算效率最高的方式——位运算60
3.1.10 用一维数组代替二维数组62
3.1.11 布尔运算代替位运算64
3.1.12 提取表达式优化65
3.1.13 不要总是使用取反操作符!66
3.1.14 不要重复初始化变量66
3.1.15 变量初始化过程思考66
3.1.16 对象的创建、访问过程69
3.1.17 在switch语句中使用字符串70
3.1.18 数值字面量的改进73
3.1.19 优化变长参数的方法调用74
3.1.20 针对基本数据类型的优化75
3.1.21 空变量76
3.2 集合类概念77
3.2.1 快速删除List里面的数据78
3.2.2 集合内部避免返回null80
3.2.3 ArrayList、LinkedList比较82
3.2.4 Vector、HashTable比较85
3.2.5 HashMap使用经验87
3.2.6 EnumSet、EnumMap91
3.2.7 HashSet使用经验92
3.2.8 LinkedHashMap、TreeMap比较96
3.2.9 集合处理优化新方案99
3.2.10 优先考虑并行计算107
3.3 字符串概念108
3.3.1 String对象108
3.3.2 善用String对象的SubString方法111
3.3.3 用charat代替startswith113
3.3.4 在字符串相加的时候,使用' '代替" "114
3.3.5 字符串切割114
3.3.6 字符串重编码117
3.3.7 合并字符串118
3.3.8 正则表达式不是万能的122
3.4 引用类型概念123
3.4.1 强引用(Strong Reference)126
3.4.2 软引用(Soft Reference)131
3.4.3 弱引用(Weak Reference)135
3.4.4 引用队列141
3.4.5 虚引用(Phantom Reference)142
3.5 其他相关概念146
3.5.1 JNI技术提升146
3.5.2 异常捕获机制150
3.5.3 ExceptionUtils类154
3.5.4 循环技巧155
3.5.5 替换switch157
3.5.6 优化循环158
3.5.7 使用arrayCopy159
3.5.8 使用Buffer进行IO操作161
3.5.9 使用clone代替new164
3.5.10 IO速度166
3.5.11 Finally方法里面释放或者关闭资源占用167
3.5.12 资源管理机制167
3.5.13 牺牲CPU时间169
3.5.14 对象操作172
3.5.15 正则表达式172
3.5.16 压缩文件处理174
3.6 本章小结175
第4章 程序设计优化建议176
4.1 算法优化概述176
4.1.1 常用算法逻辑描述177
4.1.2 多核算法优化原理186
4.1.3 Java算法优化实践188
4.2 设计模式196
4.2.1 设计模式的六大准则196
4.2.2 单一对象控制200
4.2.3 并行程序设计模式202
4.2.4 接口适配205
4.2.5 访问方式隔离219
4.3 IO及网络相关优化225
4.3.1 IO操作优化225
4.3.2 Socket编程231
4.3.3 NIO 2.0文件系统235
4.4 数据应用优化236
4.4.1 关系型数据库优化236
4.4.2 向HBase插入大量数据240
4.4.3 解决海量数据缓存251
4.5 其他优化256
4.5.1 Web系统性能优化建议256
4.5.2 死锁情况解决方案259
4.5.3 JavaBeans组件268
4.6 本章小结269
第5章 Java并行程序优化建议270
5.1 并行程序优化概述270
5.1.1 资源限制带来的挑战271
5.1.2 进程、线程、协程272
5.1.3 使用多线程的原因281
5.1.4 线程不安全范例282
5.1.5 重排序机制284
5.1.6 实例变量的数据共享286
5.1.7 生产者与消费者模式288
5.1.8 线程池的使用290
5.2 锁机制对比296
5.2.1 锁机制概述296
5.2.2 Synchronized使用技巧298
5.2.3 Volatile的使用技巧303
5.2.4 队列同步器304
5.2.5 可重入锁307
5.2.6 读写锁308
5.2.7 偏向锁和轻量级锁309
5.3 增加程序并行性310
5.3.1 并发计数器311
5.3.2 减少上下文切换次数312
5.3.3 针对Thread类的更新314
5.3.4 ForkJoin框架314
5.3.5 Executor框架318
5.4 JDK类库使用319
5.4.1 原子值320
5.4.2 并行容器324
5.4.3 非阻塞队列332
5.4.4 阻塞队列338
5.4.5 并发工具类365
5.5 本章小结376
第6章 JVM性能测试及监控377
6.1 监控计算机设备层378
6.1.1 监控CPU380
6.1.2 监控内存405
6.1.3 监控磁盘417
6.1.4 监控网络423
6.2 监控JVM活动428
6.2.1 监控垃圾收集目的429
6.2.2 GC垃圾回收报告分析430
6.2.3 图形化工具431
6.2.4 GC跟踪示例437
6.3 本章小结438
第7章 JVM性能调优建议439
7.1 JVM相关概念439
7.1.1 内存使用相关概念440
7.1.2 字节码相关知识443
7.1.3 自动内存管理448
7.2 JVM系统架构451
7.2.1 JVM的基本架构451
7.2.2 JVM初始化过程453
7.2.3 JVM架构模型与执行引擎456
7.2.4 解释器与JIT编译器456
7.2.5 类加载机制457
7.2.6 虚拟机458
7.3 垃圾回收机制相关459
7.3.1 GC相关概念459
7.3.2 垃圾回收算法468
7.3.3 垃圾收集器476
7.4 实用JVM实验490
7.4.1 将新对象预留在年轻代490
7.4.2 大对象进入年老代494
7.4.3 设置对象进入年老代的年龄495
7.4.4 稳定与震荡的堆大小497
7.4.5 吞吐量优先案例498
7.4.6 使用大页案例499
7.4.7 降低停顿案例499
7.4.8 设置最大堆内存499
7.4.9 设置最小堆内存500
7.4.10 设置年轻代503
7.4.11 设置持久代504
7.4.12 设置线程栈504
7.4.13 堆的比例分配505
7.4.14 堆分配参数总结508
7.4.15 垃圾回收器相关参数总结509
7.4.16 查询GC命令515
7.5 本章小结515
第8章 其他优化建议516
8.1 Java现有机制及未来发展516
8.1.1 Java体系结构变化历史516
8.1.2 Java语言面临的挑战520
8.1.3 Java 8的新特性522
8.1.4 Java语言前景523
8.1.5 物联网:Java和你是一对524
8.1.6 Java模块化发展525
8.1.7 OpenJDK的发展527
8.2 系统架构优化建议528
8.2.1 系统架构调优528
8.2.2 Java项目优化方式分享530
8.2.3 面向服务架构534
8.2.4 程序隔离技术538
8.2.5 团队并行开发准则544
8.3 与编程无关546
8.3.1 工程师品格546
8.3.2 如何成为技术大牛547
8.3.3 编程方法分享548
8.4 本章小结549
|
內容試閱:
|
8.2.2.1 一般性软件项目优化案例
假设我们有这么一个项目,外部系统D通过系统对外提供的REST
API接口从系统内部获取信息,从中提取出有效的信息,并通过JDBC存储到某数据库系统S中,以便供系统其他部分使用,上述操作的执行频率为每天一次,一般在午夜当系统空闲时定时执行。为了实现高可用性(High Availability),外部系统D部署在两台服务器上,因此需要分别从这两台服务器上获取信息并将信息插入数据库中,有效信息的条数达到了上千条,数据库插入操作次数则为有效信息条数的两倍。系统架构图如图8-1所示。
图8-1 系统架构图
为了快速地实现预期效果,在最初的实现中优先考虑了功能的实现,而未考虑系统性能和代码可读性等。系统大致有以下的实现。
(1)REST API获取信息、数据库操作可能抛出的异常信息都被记录到日志文件中,作为调试用。
(2)共有5次数据库连接操作,包括第一次清空数据库表,针对两个外部系统D各有两次数据库插入操作,这5个连接都是独立的,用完之后即释放。
(3)所有的数据库插入语句都是使用java.sql.Statement类生成的。
(4)所有的数据库插入语句,都是单条执行的,即生成一条执行一条。
(5)整个过程都是在单个线程中执行的,包括数据库表清空操作,数据库插入操作,释放数据库连接。
(6)数据库插入操作的JDBC代码散布在代码中。虽然这个版本的系统可以正常运行,达到了预期的效果,但是效率很低,从通过 REST API获取信息,到解析并提取有效信息,再到数据库插入操作,总共耗时100秒左右。而预期的时间应该在一分钟以内,这显然是不符合要求的。
开始分析整个过程有哪些耗时操作,以及如何提升效率,缩短程序执行的时间。通过REST API获取信息,因为是使用外部系统提供的API,所以无法在此处提升效率;取得信息之后解析出有效部分,因为是对特定格式的信息进行解析,所以也无效率提升的空间。综上所述,效率可以大幅度提升的空间在数据库操作部分以及程序控制部分。
针对日志的优化
因为从两台服务器的外部系统D上获取到的信息是相同的,所以数据库插入操作会抛出异常,异常信息类似于Attempt to insert duplicate record,这样的异常信息跟有效信息的条数相等,有上千条。这种情况是能预料到的,所以可以考虑关闭日志记录,或者不关闭日志记录而是更改日志输出级别,只记录严重级别(severe level)的错误信息,并将此类操作的日志级别调整为警告级别(warning level),这样就不会记录以上异常信息了。本项目使用的是 Java 自带的日志记录类,以下配置文件将日志输出级别设置为严重级别。
通过上述的优化之后,性能有了大幅度的提升,从原来的100秒左右降到了50秒左右。为什么仅仅不记录日志就能有如此大幅度的性能提升呢?查阅资料,发现已经有人做了相关的研究与实验。经常听到Java程序比CC 程序慢的言论,但是运行速度慢的真正原因是什么,估计很多人并不清楚。对于CPU密集型的程序(即程序中包含大量计算),Java程序可以达到CC 程序同等级别的速度,但是对于IO密集型的程序(即程序中包含大量IO操作),Java程序的速度就远远慢于CC 程序了,很大程度上是因为CC 程序能直接访问底层的存储设备。因此,不记录日志而得到大幅度性能提升的原因是,Java程序的IO操作较慢,是一个很耗时的操作。
针对数据库连接的优化
假设程序中共有若干次数据库连接操作,每次都需重新建立数据库连接,数据库插入操作完成之后又立即释放了,数据库连接没有被复用。为了做到共享数据库连接,可以通过单例模式
(Singleton Pattern)获得一个相同的数据库连接,每次数据库连接操作都共享这个数据库连接。这里没有使用数据库连接池(Database Connection Pool)是因为在程序只有少量的数据库连接操作,只有在大量并发数据库连接的时候才需要连接池。
通过上述的优化之后,性能有了小幅度的提升,从50秒左右降到了40秒左右。共享数据库连接而得到的性能提升的原因是,数据库连接是一个耗时耗资源的操作,需要同远程计算机进行网络通信,建立TCP连接,还需要维护连接状态表,建立数据缓冲区。如果共享数据库连接,则只需要进行一次数据库连接操作,省去了多次重新建立数据库连接的时间。
针对数据库插入数据的优化
这个假想项目内部存在大量的数据库插入操作,所以使用预编译SQL方式。具体做法是使用java.sql.PreparedStatement代替java.sql.Statement生成SQL语句。PreparedStatement使得数据库预先编译好SQL语句,并用来传入参数。而Statement生成的SQL语句在每次提交时,数据库都需进行编译。在执行大量类似的SQL语句时,可以使用PreparedStatement提高执行效率。使用PreparedStatement的另一个好处是不需要拼接SQL语句,代码的可读性更强。通过上述的优化之后,性能有了小幅度的提升,从40秒左右降到了30~35秒左右。
此外,我们可以使用SQL批处理方式加快SQL执行。通过java.sql.PreparedStatement的addBatch方法将SQL语句加入到批处理,这样在调用execute方法时,就会一次性地执行SQL批处理,而不是逐条执行。通过上述的优化之后,性能有了小幅度的提升,从30~35秒左右降到了30秒左右。
针对多线程的优化
清空数据库表的操作,把从两个外部系统D取得的数据插入数据库记录的操作,是相互独立的任务,可以给每个任务分配一个线程执行。清空数据库表的操作应该先于数据库插入操作完成,可以通过java.lang.Thread类的join方法控制线程执行的先后次序。在单核CPU时代,操作系统中某一时刻只有一个线程在运行,通过进程线程调度,给每个线程分配一小段执行的时间片,可以实现多个进程线程的并发(concurrent)执行。而在目前的多核多处理器背景下,操作系统中同一时刻可以有多个线程并行(parallel)执行,大大地提高了计算速度。
此外,通过采用不同的锁机制,也能加快程序的执行,具体请见第5章。
通过上述的优化之后,性能有了大幅度的提升,从30秒左右降到了15秒以下,10~15秒之间。使用多线程而得到的性能提升的原因是,系统部署所在的服务器是多核多处理器的,使用多线程,给每个任务分配一个线程执行,可以充分地利用CPU计算资源。
针对设计模式的优化
原来的代码中混杂着JDBC操作数据库的代码,代码结构显得十分凌乱。通过使用DAO模式(Data Access
Object Pattern)可以抽象出数据访问层,这样使得程序可以独立于不同的数据库,即便访问数据库的代码发生了改变,上层调用数据访问的代码无须改变。并且程
序员可以摆脱单调烦琐的数据库代码的编写,专注于业务逻辑层面的代码的开发。通过上述的优化之后,性能并未有提升,但是代码的可读性、可扩展性大大地提高了。
总的来说,通过关闭日志记录、共享数据库连接、使用预编译SQL、使用SQL批处理、使用多线程实现并发并行、使用DAO模式抽象出数据访问层,程序运行时间较大幅度地被缩短了,在性能上得到了很大的提升,同时也具有了更好的可读性和可扩展性。
|
|