原来地址
http://blog.csdn.net/jaminwm/archive/2007/03/26/1541767.aspx
调优背景
HBCZT信息中心使用
IBM X366
服务器
Windows2003
运行其基于
J2EE1.4
技术的应用系统。另外运行一个基于
COM
技术的数据采集应用程序。该程序客户端读入用户填写的
xls
格式表格文件信息,并通过该程序将
XLS
内容封装成为
XML
并打包
ZIP
后发送到数据采集程序的服务器端,服务器端接受到文件后,对该
ZIP
包进行解包、并对解包后的
XML
信息进行解析、使用
SQL
逐条将记录插入到
Oracle
数据库中。数据库连接池已经设置为
20
,但批量数据插入数据库的时候(数据量至少
500000
条记录,一般情况
5000000
条记录)导致数据库异常缓慢。客户希望找到系统瓶颈,并提出相应性能调优建议。
l
总体思路
硬件调优、操作系统调优,数据库调优
略!我们假设都已经是最佳状态。由于本人负责
WebLogic
部分的调优,所以以下思路与内容均为
WebLogic
方面。特此说明
J2EE
应用架构环境下的系统调优,首先我们一般会从应用程序出发,去审核代码,做到代码级的优化,然后再调整应用服务器
(BEA WebLogic8.1)
和数据库
(Oracle9i)
的参数,最后当然是调整操作系统和网络的性能
(
包括硬件升级
)
。这是一种
MDA
的先进做法。诚然,在这样一个政务项目中,不可能完全按照这个思路来做,我们把目标首先定位在应用系统所在的应用服务器
(BEA WebLogic8.1)
上,通过对
BEA WebLogic8.1
的参数进行设置,使
WebLogic8.1
能够在最优化的环境中去运行其系统,然后对
ORACLE
数据的参数进行优化设置,最后进行性能测试再找出导致性能瓶颈所在的
SQL
代码或
JAVA
程序,考量其修改的可行性,并进行最终问题优先级认定,与瓶颈模块进行协商解决性能问题。当然,一般情况下我见过的案例都是出现了性能问题后才想到调优,而且一般都是先进行系统参数调整,实在解决不了才会对代码进行检查
.
实际上,我们应当将代码级的调优放在应用设计时来做,测试生产时修改代码将是一件极其痛苦的事情。
下表为一般性
J2EE
性能调优的参照情况一览表,供参考。
毛病
|
描述
|
症状
|
原因或治法
|
线性内存泄漏
|
每单位
(
每事务、每用户等
)
泄漏造成内存随着时间或负载线性增长。这会随着时间或负载增长降低系统性能。只有重启才有可能恢复。
|
随着时间越来越慢
随着负载越来越慢
|
虽然可能有多种外部原因,但最典型的是与资源泄漏有关
(
例如,每单位数据的链表存储,或者没有回收的回收
/
增长缓冲区
)
。
|
指数方式内存泄漏
|
双倍增长策略的泄漏造成系统内存消耗表现为时间的指数曲线
|
随着时间越来越慢
随着负载越来越慢
|
通常是由于向集合
(Vector
,
HashMap)
中加入永远不删除的元素造成的。
|
糟糕的编码:无限循环
|
线程在
while(true)
语句以及类似的语句里阻塞。
|
可以预见的锁定
|
您需要对循环进行大刀阔斧的删剪。
|
资源泄漏
|
JDBC
语句,
CICS
事务网关连接,以及类似的东西被泄漏了,造成对
Java
桥接层和后端系统的影响。
|
随着时间越来越慢
可以预见的锁定
突然混乱
|
通常情况下,这是由于遗漏了
finally
块,或者更简单点,就是忘记用
close()
关闭代表外部资源的对象所造成的。
|
外部瓶颈问题
|
后端或者其他外部系统(如鉴权)越来越慢,同样减缓了
J2EE
应用服务器和应用程序
|
持续缓慢
随着负载越来越慢
|
咨询专家(负责的第三方或者系统管理员),获取解决外部瓶颈问题的方法。
|
外部系统
|
J2EE
应用程序通过太大或太多的请求滥用后端系统。
|
持续缓慢
随着负载越来越慢
|
清除冗余的工作请求
,成批处理相似的工作请求,把大的请求分解成若干个更小的请求,调整工作请求或后端系统
(
例如,公共查询关键字的索引
)
等。
|
糟糕的编码:
CPU
密集的组件
|
这是
J2EE
世界中常见的感冒。一些糟糕的代码或大量代码之间一次糟糕的交互,就挂起了
CPU
,把吞吐速度减慢到爬行的速度。
|
持续缓慢
随着负载越来越慢
|
典型的解决方案就是数据高速缓存或者性能计数。
|
中间层问题
|
实现得很糟糕的桥接层
(JDBC
驱动程序,到传统系统的
CORBA
连接
)
,由于对数据和请求不断的排列、解除排列,从而把所有通过它的流量减慢到爬行速度。这个毛病在早期阶段很容易与外部瓶颈混淆。
|
持续缓慢
随着负载越来越慢
|
检查桥接层和外部系统的版本兼容性。如果有可能,评估不同的桥接供应商。如果重新规划架构,有可能完全不需要桥接。
|
内部资源瓶颈:过度使用或分配不足
|
内部资源
(
线程、放入池的对象
)
变得稀缺。是在正确使用的情况下加大负载时出现过度使用还是因为泄漏?
|
随着负载越来越慢
零星的挂起或异常错误
|
分配不足:根据预期的最大负载提高池的最大尺寸。过度使用:请参阅外部系统的过度使用。
|
不停止的重试
|
这包括对失败请求连续的
(
或者在极端情况下无休止的
)
重试。
|
可以预见的锁定
突然混乱
|
可能就是后端系统完全宕机。在这里,可用性监控会有帮助,或者就是把尝试与成功分开。
|
线程:阻塞点
|
线程在过于积极的同步点上备份,造成交通阻塞。
|
随着负载越来越慢
零星的挂起或异常错误
可以预见的锁定
突然混乱
|
可能同步是不必要的
(
只要重新设计
)
,或者比较外在的锁定策略
(
例如,读
/
写锁
)
也许会有帮助。
|
线程:死锁
/
活动锁
|
最普遍,这是您基本的“获得顺序”的问题。
|
突然混乱
|
处理选项包括:主锁,确定的获得顺序,以及银行家算法。
|
l
调优建议
通过分析其配置。我们发现
JDBC
连接池存在性能问题。
在
WebLogic
中就大量使用了池
:JDBC Connection Pool
、
Socket Pool
、
Object Pool
和
Thread Pool
。
I/O
操作中,
buffer
是必须的,特别是对大文件的操作,不然容易造成内存溢出。字节操作最快,所以尽可能采用
write(byte[])
,
Buffered FileOutputStream
比
Buffered FileWriter
要快,因为
FileWriter
需要
Unicode
到
Byte
的转换。
JDBC
建议使用
buffer
和
cache
。为
HttpServletResponse
设置
buffersize
,使用
wl-cache
,缓存在
JNDI
树上获取的对象等等。
此外,使用
JDK 1.4
的非阻塞
I/O
对性能也有很大提高。
JDBC
代码调优最大的原则就是使用
WebLogic
的连接池,而不是自己直连数据库。在我接触的很多自己实现连接池的项目中,大部分遇到死锁和连接泄漏的问题,最后得不得修改代码。而
WebLogic
提供了功能强大,性能良好的数据库连接池,我们要做的只是封装一个连接管理类,从
JNDI
树上获取数据源并缓存,得到连接,并提供一系列关闭数据库资源的方法。
对任何资源使用的原则是用完即关,不管是数据库资源、上下文环境,还是文件。数据库资源的泄漏极易造成内存泄漏,乃至系统崩溃。在使用完数据库资源后依次关闭
ResultSet
,
Statement
和
Connection
,而在一个数据库连接多次进行数据库操作时要特别注意
ResultSet
和
Statement
依次关闭。
由于获取连接时默认自动提交方式,使用
connection.setAutoCommit(false)
关闭自动提交,使用
PreparedStatement
,批量更新,业务复杂或者大数据量操作时使用存储过程,尽量使用
RowSet
,此外设置记录集读取缓存
FetchSize
和设置记录集读取方向
FetchDirection
对性能也有一定的提高。
Servlet
代码调优比较简单:在
Servlet
之间跳转时,
forward
比
sendRedirect
更有效;设置
HttpServletResponse
缓冲区,如:
response.setBufferSize(20000);
在
init()
方法里缓存静态数据,而在
destroy()
中释放它;建议在
Servlet
里使用
ServletOutputStream
输出图片等对象;避免在
Servlet
和
Jsp
中定界事务等。
JDBC Connection Pool
的调优受制于
WebLogic Server
线程数的设置和数据库进程数,游标的大小。通常我们在一个线程中使用一个连接,所以连接数并不是越多越好,为避免两边的资源消耗,建议设置连接池的最大值等于或者略小于线程数。同时为了减少新建连接的开销,将最小值和最大值设为一致。增加
Statement Cache Size
对于大量使用
PreparedStatement
对象的应用程序很有帮助,
WebLogic
能够为每一个连接缓存这些对象,此值默认为
10
。在保证数据库游标大小足够的前提下,可以根据需要提高
Statement Cache Size
。比如当你设置连接数为
25
,
Cache Size
为
10
时,数据库可能需要打开
25*10=250
个游标。不幸的是,当遇到与
PreparedStatement Cache
有关的应用程序错误时,你需要将
Cache Size
设置为
0
。尽管
JDBC Connection Pool
提供了很多高级参数,在开发模式下比较有用,但大部分在生产环境下不需调整。这里建议最好不要设置测试表,
同时
Test Reserved Connections
和
Test Released Connections
也无需勾上。
当然如果你的数据库不稳定,时断时续,你就可能需要上述的参数打开。
最后分析一下
JDBC
驱动程序类型的选择,
Oracle
提供
thin
驱动和
oci
驱动,从性能上来讲,
oci
驱动强于
thin
驱动,特别是大数据量的操作。但在简单的数据库操作中,性能相差不大。所以我建议对数据量至少
500000
条记录,一般情况
5000000
条记录的状况使用
oci
驱动。
通过分析其日志并使用
GC
资源查看。我们发现存在内存泄露的垃圾回收失败问题。
垃圾收集
(GC)
是指
JVM
释放
Java
堆中不再使用的对象所占用的内存的过程,而
Java
堆
(Heap)
是指
Java
应用程序对象生存的空间。堆大小决定了
GC
的频度和时间。堆越大,
GC
频度低,速度慢。堆越小,
GC
频度高,速度快。所以
GC
和堆大小是一组矛盾。为了获取理想的
Heap
堆大小,需要使用
-verbosegc
参数
(Sun jdk: -Xloggc:)
以打开详细的
GC
输出。分析
GC
的频度和时间,结合应用最大负载所需内存情况,得出堆的大小。通常情况下,我们建议使用可用内存
(
除操作系统和其他应用程序占用之外的内存
)70-80%
,为避免堆大小调整引起的开销,设置内存堆的最小值等于最大值即
:-Xms=-Xmx
。而为了防止内存溢出,建议在生产环境堆大小至少为
256M(Platform
至少
512M)
,实际环境中
512M~1G
左右性能最佳,
2G
以上是不可取的,在调整内存时可能需要调整核心参数进程的允许最大内存数。对于
sun
和
hp
的
jvm
,永久域太小
(
默认
4M)
也可能造成内存溢出,应增加参
-XX:MaxPermSize=128m
。建议设置临时域
-Xmn
的大小为
-Xmx
的
1/4~1/3
,
SurvivorRatio
为
8
。
为了获得更好的性能,建议在启动文件设置
WebLogic
为产品模式,此时
sun
和
hp jvm JIT
引擎为
-server
,默认情况下打开
JIT
编译模式对性能也有帮助。调整
Chunk Size
和
Chunk Pool Size
也可能对系统的吞吐量有提高。此外还需关闭显示
GC: -XX:+DisableExplicitGC
。
建议
Intel
平台上使用
jRockit
(使用参数
-jrockit
)无疑大大提高
WebLogic
性能。本系统使用
SUN JDK1.4.2_08
。
jRockit
支持四种垃圾收集器:分代复制收集器、单空间并发收集器、分代并发收集器和并行收集器。默认状态下,
JRockit
使用分代并发收集器。要改变收集器,可使用
-Xgc:
,对应四个收集器分其他为
gencopy
,
singlecom
,
gencon
以及
parallel
。为得到更好的响应性能,应该使用并发垃圾回收器:
-Xgc:gencon
,可使用
-Xms
和
-Xmx
设置堆栈的初始大小和最大值,要设置护理域
-Xns
为
-Xmx
的
10%
。而如果要得到更好的性能,应该选用并行垃圾回收器
:-Xgc: parallel
,由于并行垃圾回收器不使用
nursery
,不必设置
-Xns
。
jRockit
还提供了强大的图形化监控工具
Jrockit Management Console
。可以查看
GC
性能问题。
通过实时查看并分析操作系统与
ORACLE
系统中的
I/O
信息。我们发现存在
I/O
读写占用资源率高且频繁问题。
WebLogic Server有两套套接字复用器:Java版和本地库。采用小型本地库更有效,尽量激活Enable Native IO(默认),此时UNIX默认使用CPUs+1个线程,Window下为双倍CPU。如果系统不能加载本地库,将会抛出一个异常:java.lang.UnsatisfiedLinkException,此时只能使用Java套接字复用器,可以调整socket readers 百分比,默认为33%。该参数可以在Console Server Tuning Configuration配置栏里设置。