现在的位置: 首页 > 综合 > 正文

性能调优指南–计算机系统Java应用程序V0.1

2018年04月06日 ⁄ 综合 ⁄ 共 37139字 ⁄ 字号 评论关闭
文章目录

写blog和写代码一样,刚开始都是不完美的,需要不断的修正和重构,如果大家在阅读本blog中发现任何问题和疑问,都欢迎讨论或拍砖。
转载请保持原链接地址:http://blog.csdn.net/ajian005/article/details/18910685
本文代码示例JavaPerformanceOptimizerDemo.rar下载地址:http://download.csdn.net/detail/ajian005/6907817
补充历史记录:

[1] 2014-2-15 12:13 linux下查看根据进程查看线程的方法

[2] 2014-6-10 23:39 4.4 网络IO消耗分析 增加:linux内核参数的优化

1 性能调优简介

1.1为什么要进行性能调优?

1.1.1 编写的新应用上线前在性能上无法满足需求,这个时候需要对系统进行性能调优

1.1.2 应用系统在线上运行后随着系统数据量的不断增长、访问量的不断上升,系统的响应速度通常越来越慢,不满足业务需要,这个时候也需要对系统进行性能调优

1.2 性能调优包括那些方面?

1.2.1 谈到系统或产品的性能调优,可以从广义和狭义两个范围来理解。

从广义的层面来看,就不仅限于程序内部了,因为造成系统性能问题的瓶颈很可能来源于方方面面,而这种情况往往是性能调优很普遍的情况,

下面就从广义的范围细分成几个角度来进行阐述,见图-1 广义性能调优鱼骨图

图-1 广义性能调优鱼骨图

1.2.2从狭义的范畴来看,性能调优主要是指通过修改软件程序逻辑、结构等技术手段提升软件产品的各项性能指标,如响应时间等。本文重要是从狭义的范畴来看。

从狭义的范畴看,性能调优可以从 硬件(计算机体系机构)、操作系统(OS\JVM)、文件系统、网络通信、数据库系统、中间件、应用程序本身等方面入手。

这里主要关注JVM、中间件、应用程序的性能调优。

1.3 性能的参考指标

执行时间:一段代码从开始运行到运行结束所使用的时间。

CPU时间:(算法)函数或者线程占用CPU的时间。

内存分配:程序在运行时占用的内存空间。

磁盘吞吐量:描述I/O的使用情况。

网络吞吐量:描述网络的使用情况。

响应时间:系统对某用户行为或者动作做出响应的时间。响应时间越短,性能好。

2 性能调优步骤

2.1性能调优步骤图

图-2性能调优步骤图

调优前首先要做的是衡量系统现状,这包括目前系统的请求次数、响应时间、资源消耗等信息,例如B系统目前95%的请求响应时间为1秒。

在有了系统现状后可设定调优目标,通常调优目标是根据用户所能接受的响应速度或系统所拥有的机器以及所支撑的用户量估算出来的,例如 设定调优目标:95%的请求要在500ms内返回。

在设定了调优目标后,需要做的是寻找性能瓶颈,这一步最重要的是找出造成目前系统性能不足的最大瓶颈点。找出后,可结合一些工具来找出造成瓶颈点的代码,到此才完成了这个步骤。

在找到了造成瓶颈点的代码后,开始进行性能调优。通常需要分析其需求或业务场景,然后结合一些优化的技巧确定优化的策略,优化策略或简或繁,选择其中收益比(优化后的预期效果/优化需要付出的代价)最高的优化方案,进行优化。

优化部署后,继续衡量系统的状况,如已达到目标,则可结束此次调优,如仍未达到目标,则要看是否产生了新的性能瓶颈。或可以考虑继续尝试上一步中制定的其他优化方案,直到达成调优目标或论证在目前的体系结构上无法达到调优目标为止。

3 性能调优思路

性能调优的步骤主要有:衡量系统现状、设定调优目标、寻找性能瓶颈、性能调优,验证是否达到调优目标。

本文档主要关注调优步骤中的 寻找性能瓶颈性能调整(优) 两个关键的阶段阶段,主要思路见下图:图-3 寻找性能瓶颈和性能调优项结构分解图

图-3 寻找性能瓶颈和性能调优项结构分解图

4 寻找性能瓶颈

寻找性能瓶颈分个两部分:寻找过度消耗资源的代码 和 寻找未充分使用资源但程序执行慢的原因和代码。

通常性能瓶颈的表象是资源消耗过多外部处理系统的性能不足;或者资源消耗不多但程序的响应速度却仍达不到要求。

这里资源主要是指:消耗在CPU、内存、文件IO、网络IO等方面资源。机器的资源是有限的,当某资源消耗过多时,通常会造成系统的响应速度慢。

外部处理系统的性能不足主要是指所调用的其他系统提供的功能(如数据库操作的响应速度不够);所调用的其他系统性能不足多数情况下也是资源消耗过多,但程序的性能不足造成的; 数据库操作性能不足通常可以根据数据库的sql执行速度、数据库机器的IOPS、数据库的Active Sessions等分析出来。

寻找未充分使用资源但程序执行慢的原因和代码:资源消耗不多、但程序的响应速度仍然达不到要求的主要原因是程序代码运行效率不够高、未充分使用资源或程序结构不合理。

对于Java应用而言,寻找性能瓶颈的方法通常为首先分析资源的消耗,然后结合OS和Java等一些分析工具来查找程序中造成资源消耗过多的代码。

4.1CPU消耗分析

在Linux中,CPU主要用于 中断、内核进程以及用户进程的任务处理,优先级为:中断 >  内核进程 > 用户进程,在学习如何分析CPU消耗状况前,有三个重要概念交代一下。

4.1.1 上下文切换、运行队列、利用率

4.1.1.1上下文切换

每个CPU(或多核CPU中的每核CPU)在同一时间只能执行一个线程。 Linux采用的是抢占式调度:即为每个线程分配一定的执行时间,当到达执行时间、线程中有IO阻塞或高优先级线程要执行时,Linux将切换执行的线程,在切换时要存储目前程序的执行状态PCB(Program Control Block),并恢复要执行的线程的状态,这个过程就称为上下文切换。

对于Java应用,典型的是在进行文件IO操作、网络IO操作、锁等待或线程Sleep时,当前线程会进入阻塞或休眠状态,从而触发上下文切换,上下文切换过多会造成内核占据较多的CPU使用,使得应用的响应速度下降。

4.1.1.2运行队列

每个CPU核都维护了一个可运行的线程队列,例如一个4核的CPU, Java应用中启动了8个线程,且这8个线程都处于可运行状态,那么在分配平均的情况下每个CPU中的运行队列里就会有两个线程。通常而言,系统的load主要由CPU的运行队列来决定,假设以上状况维持了1分钟,那么这1分钟内系统的load就会是2,但由于load是个复杂的值,因此也不是绝对的,运行队列值越大,就意味着线程要消耗越长的时间才能执行完。 Linux System and NewWork Performance Monitoring中建议控制在每个CPU核上的运行队列为1-3个。

4.1.1.3利用率

CPU利用率为CPU在用户进程、内核进程、中断处理、IO等待以及空闲五个部分使用的百分比,这五个值是用来分析CPU消耗情况的关键指标。 Linux System and NewWork Performance Monitoring 中建议用户进程的CPU消耗/内核的CPU消耗的比例在 65%-70% / 30%-35%

4.1.2Linux观测CPU消耗状态的工具:perf、top、vmstat、pidstat、sar、pcpu、ps Hh -eo tid 

4.1.2.0 工具 perf 性能测试工具

  sudo apt-get install linux-tools-common
  sudo apt-get install linux-tools-3.13.0-27-generic

4.1.2.1工具top

用工具SSH登陆到Linux 上后,在字符界面下输入top命令后即可查看CPU的消耗情况,CPU的信息在TOP视图的上面几行中

图-4 Top查看CPU使用情况图:第四行被矩形围起来的部分:其中 0.2% us 表示用户进程处理所占的百分比;0.7% sy 表示为内核线程处理所占的百分比; 0.0% ni 表示被nice命令改变优先级的任务所占的百分比;99.0% id 表示CPU的idle空闲时间所占的百分比; 0.0% wa 表示在执行的过程中等待IO所占的百分比; 0.2% hi 表示硬件中断所占的百分比; 0.0% si
表示软件中断所占的百分比。

图-4 Top查看CPU使用情况图

linux下查看根据进程查看线程的方法

1、cat /proc/${pid}/status
2、pstree -p ${pid}
3、3.1 top -p ${pid} 再top 中键入[Shift]-[H]组合键。
      3.2 或者直接输入 top -bH -d 3 -p  ${pid}
      top -H 手册中说:-H : Threads toggle
      加上这个选项启动top,top一行显示一个线程。否则,它一行显示一个进程。

    1 先用ps + grep找出该死的进程pid,比如 30420
    2 top -H -p 30420,(top然后shift+H可以看出某个线程)所有该进程的线程都列出来了。看看哪个线程pid占用最多,然后将这个pid转换为16进制,如 44bf,注意要小写
    3 jstack 30420 | less,然后查找 nid=0x44bf,,找到了

4、ps xH
     手册中说:H Show threads as if they were processes
     这样可以查看所有存在的线程。
5、ps -mp <PID>
     手册中说:m Show threads after processes
     这样可以查看一个进程起的线程数。

4.1.2.2工具pidstat

pidstat 是SYSSTAT中的工具.,如需使用pidstat,请先安装SYSSTAT:http://www.icewalkers.com/Linux/Software/59040/sysstat.html

安装方法:  root$  sudo apt-get install sysstat

SYSSTAT包含工具有:sysstat, sar, sadf, iostat, pidstat,mpstat, nfsiostat and cifsiostat commands for Linux.

使用例子:

  sar -n DEV 1 100
  sar -n DEV 1 100 | grep lo

输入pidstat 1 2,在console上将会每隔1秒输出目前活动进程的CPU消耗状况,共输出2次,结果图-5 pidstat使用示例图 所示:

图-5 pidstat使用示例图

其中CPU表示的为当前进程所使用到的CPU个数,

如须查看进程中线程的CPU消耗状况,可以输入 pidstat -p [PID] -t 1 5 这样的方式来查看,执行后的输出如下:

图-6

图中的TID即为线程ID,较之top命令方式而言, pidstat的好处为可查看每个线程的具体CPU利用率的状况( 例如%usr 、%system  )

4.1.2.3CPU消耗方面相关工具介绍

除了top、pidstat外、linux中还可以使用vmstat来采样(例如每秒 vmstat 1)查看CPU的上下文切换、运行队列、利用率的具体信息。
ps Hh -eo tid,pcpu 方式也可用来查看具体线程的CPU消耗状况;sar来查看一定时间范围内以及历史的cpu消耗状况信息。
SYSSTAT包含工具有:Sysstat, sar, sadf, iostat, pidstat,mpstat, nfsiostat and cifsiostat commands for Linux.
The sysstat package contains the sar, sadf, iostat, nfsiostat, cifsiostat, pidstat and mpstat commands for Linux.
The sar command collects and reports system activity information. The information collected by sar can be saved in a file in a binary format for future inspection. The
statistics reported by sar concern I/O transfer rates, paging activity, process-related activities, interrupts, network activity, memory and swap space utilization, CPU utilization, kernel activities and TTY statistics, among others.
The sadf command may be used to display data collected by sar in various formats (CSV, XML, database-friendly, etc.).
The iostat command reports CPU utilization and I/O statistics for disks and network filesystems.
The pidstat command reports statistics for Linux tasks (processes).
The mpstat command reports global and per-processor statistics. Both UP and SMP machines are fully supported. Sysstat has also support for hotplug CPU's, and for National Language (NLS).
The nfsiostat command reports I/O statistics for network filesystems.
The cifsiostat command reports I/O statistics for CIFS filesystems.

当CPU消耗严重时,主要体现在us、sy、wa 或 hi的值变高, wa的值是IO等待造成的,hi的值变高主要为硬件终端造成的,例如网卡接受数据频繁的状况。

对于Java应用而言,CPU消耗严重主要体现在us、sy两个值上,分别看看Java应用在这两个值高的情况下应如何找到对应造成瓶颈的代码。

4.1.3 us (CPU的用户进程处理所占的百分比)

当us值过高时,表示运行的应用消耗了大部分的CPU. 在这种情况下,对于Java应用而言,最重要的是找到具体消耗CPU的线程及锁执行的代码。 

pstack显示每个进程的栈跟踪

pstree以树结构显示进程

方法如下:

首先通过Linux提供的命令(如:top/jps找到java进程pid,然后pidstat通过进程pid找到对应的线程tid)找到消耗CPU严重的线程及其ID,将此线程ID转化为十六进制的值。

其次通过kill -3 [javapid]或jstack 的方式dump出应用的java线程信息,通过之前转化出的十六进制的值找到对应的nid值的线程。此线程即为消耗CPU的线程,在采样时须多执行几次上述的过程,以确保找到真实的消耗CPU的线程。

寻找过程:第一步:jps找到java进程号PID=2418, 然后pidstat -p 2418 -t 1 5 | less 找到消耗CPU多的线程号tid=2428,然后将tid=2428(十进制) 转换为十六进制数:97C.

图-7 jps命令图

图-8 pidstat 找到消耗CPU高的tid线程ID

第二步:jstack  2418 > jstack03.txt 将线程dump信息写入文件jstack03.txt, 然后将十六进制的线程数:97C  为79C为查询条件,到jstack03.txt文件中查找到nid= 97C的线程,该线程即为消耗CPU多的线程。

图-9 线程栈dump信息

从上可以看出,主要是ConsumeCPUTask的执行消耗了CPU,但由于jstack需要时间,因此基于jstack并不一定能分析出真正的消耗CPU的代码是哪行。

例如在一个操作中循环调用了很多其他的操作,如其他的操作每次都比较快,但由于循环太多次,造成了CPU消耗,在这种情况下jstack是无法捕捉出来的。最佳方式是通过 intel vtune来进行分析,vtune是商业软件(Intel芯片的一款性能分析软件,用AMD芯片的同学就不能用了)。

在不使用vtune的情况下,则只能通过认真查看整个线程中执行的动作来分析原因,例如在从上图中,可以看出ConsumeCPUTask一直处于运行状态,可以分析ConsumeCPUTask这个线程具体在做的动作,从其代码可看出真个线程一直处于运行过程中,中途没有IO中断、锁等待现象,因此造成了CPU消耗严重。

该示例源代码UsHighOfCpuDemo.java下载地址为:见参考部分

4.1.4 sy (内核线程处理所占的百分比)

当sy值高时表示Linux花费了更多的时间在进行线程切换,Java应用造成这种现象的主要原因是启动的线程比较多,且这些线程多数都处于不断的阻塞(例如锁等待、IO等待状态)和执行状态的变化过程中,这就导致了操作系统要不断地切换执行的线程,产生大量的上下文切换。在这种状况下,对Java应用而言,最重要的是找出线程不断切换状态的原因,可采用的方法为通过 kill -3 [javapid] 或jstack  -l [javapid] 的方式dump出java应用程序的线程信息,查看线程的状态信息以及锁信息,找出等待状态或锁竞争过多的线程。

第一步运行 [root@localhost example]# java -jar SyHighOfCpuDemo.jar  , top图如下: CPU的 sy使用比较高

图-10

在用vmstat -n 3 执行一下看一下上下文切换情况

图-11

由上可知,CPU在cs(内核线程上下文切换) 以及sy上消耗很大。

第二步 运行时采用jstack -l 查看程序的线程状况,可以看到启动了很多线程,并且很多的线程都经常处于 TIMED_WAITING(on object monitor)状态、WAITING (on object monitor)和Runnable状态的切换中。

通过on object monitor对应的堆栈信息,可查找到系统中锁竞争激烈的代码,这是造成系统更多时间消耗在线程上下文切换的原因。

具体TIMED_WAITING(on object monitor)状态、WAITING (on object monitor)和Runnable状态如下图

图-12

图-13

图-14

该示例源代码SyHighOfCpuDemo.java下载地址为:见参考

4.2内存消耗分析

Java应用对于内存的使用包括两方面 JVM堆内存 和 JVM堆外内存。  Java应用对内存的消耗上主要是在JVM堆内存上。在正式环境中,多数Java应用都会将 -Xms 和 -Xmx设为相同的值,避免运行期要不断申请内存。

目前Java应用只有在创建线程和使用Direct ByteBuffer 时才会操作JVM堆外的内存JVM,因此在内存消耗方面最值得关注的是JVM内存的消耗状况。对于JVM内存消耗状况分析的方法工具有:JVM(jmap、jstat、mat、visualvm等方法)。JVM内存消耗过多会导致GC执行频繁,CPU消耗增加,应用线程的执行速度严重下降,甚至造成OutOfMemoryError,最终导致Java进程退出。

 sudo -u admin  /opt/taobao/java/bin/jmap -F -dump:live,format=b,file=/home/admin/dumpcrash_20150909_2.log 6606

 sudo -u admin  /opt/company/java/bin/jmap -F -dump:file=/home/user/dumpcrash_xxx.log 1234

 /opt/company/java/bin/jmap -F -dump:file=/home/user/dumpcrash_xxx.log 1234
  jmap -histo 13321> dump_platform.core 
  jmap -dump:live,format=b,file=heap_platform.core 13321
  jmap –dump:file=<文件名>,format=b [pid] 

  分析jmap的dump文件:
Eclipse Memory Analyzer
    http://www.eclipse.org/mat/downloads.php

对于JVM堆以外的内存方面的消耗,最为值得关注的是swap的消耗 以及 物理内存的消耗,这两方面的消耗都可基于操作系统的命令来查看。

方法区Method Area内存大小对应的配置参数是:-XX:PermSize  -XX:MaxPermSize  这个抛出OutOfMemoryError: PermGen space (常量池或类太多,如反射,CGLib等)

Java堆Heap内存大小对应的配置参数是:-Xms -Xmx   这个抛出OutOfMemoryError: Java heap space

虚拟机栈VM stack内存大小对应的配置参数是:-Xss    这个抛出StackOverflowError;  OutOfMemoryError:unable to create new native thread

本地方法栈Native Method stack内存大小对应的配置参数是:-XX:DirectoryByteBuffer -XX:MaxDirectoryByteBuff    这个抛出OutOfMemoryError:  sun.misc.Unsafe.allocateMemory(Native Method)

在Java语言中,对象访问是如何进行的呢?

对象访问在Java语言中无处不在的,即使最普通的程序行为,也会涉及Java栈、Java堆、方法区这三个最重要内存区域之间的关联关系,如下代码:

  Object  obj = new Object();

假设这句代码出现在方法体中,那“Object  obj”这部分的语义将会反映到【Java栈】的本地变量表中,作为一个reference类型数据出现。

而“new Object()”这部分的语义将会反映到【Java堆】中,形成一块存储了Object类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。

另外在Java堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在【方法区Method Area】中。

类加载的步骤:

4.2.1内存消耗分析工具介绍

在Linux中可通过vmstat、sar、top、pidstat,  pmap等方式来查看 swap和物理内存的消耗状况。

4.2.1.1 vmstat

在命令行中输入vmstat,其中的信息和内存相关的主要是memory下的swpd、free、buff、cache以及swap下的si 和 so.

图-15

其中swpd是指虚拟内存已使用的部分,单位为kb;free表示空闲的物理内存;buffer表示用于缓冲的内存;cache表示用于作为缓存的内存
swap下的si是指每秒从disk读至内存的数据量;so是指每秒从内存中写入disk的数据量。
swpd值过高通常是由于物理内存不够用,操作系统将物理内存中的一部分数据转为放入硬盘上进行存储,以腾出足够的空间给当前运行的程序使用。
在目前运行的程序变化后,即从硬盘上重新读取数据到内存中,以便恢复程序的运行,这个过程会产生swap IO, 因此看swap的消耗情况主要关注的是swapIO的状况,如swapIO发生得较频繁,那么严重影响系统的性能。
由于Java应用是单进程应用,因此只要JVM的内存设置不是过大,是不会操作到swap区域的。物理内存消耗过高可能是由于JVM内存设置过大、创建的Java线程过多或通过Direct ByteBuffer往物理内存中放置了过多的对象造成的。

4.2.2.2 sar

通过sar的-r参数可查看内存的消耗状况,例如sar -r 2 5

图-16

物理内存相关的信息主要是kbmemfree、kbmemused、%memused、kbbuffers、kbcached,  当物理内存有空闲时,Linux会使用一些物理内存用于buffer和cache,  以提升系统的运行效率,因此可以认为系统中可用的物理内存为: kbmemfree + kbmemused + kbcached.
sar相比vmstat的好处是可以查询历史状况,以更加准确地分析趋势状况,例如 sar -r -f /temp/log/sa/sa12
vmstat 和 sar的共同弱点是不能分析进程所占用的内存量。

4.2.2.3 top

通过top可查看进程所消耗的内存量,不过top中看到的Java进程消耗的内存。因为Java进程是包括了JVM已分配的内存加上Java应用所耗费的JVM以外的物理内存,这会导致top中看到Java进程所消耗的内存大小有可能超过 -Xmx 加上 -XX:MaxPermSize设置的内存大小,并且Java程序在启动后也只是占据了-Xms的地址空间,但并没有占据实际的内存,只有在相应的地址空间被使用过后才被计入消耗的内存中。因此纯粹的根据top很难判断出Java进程消耗的内存中有多少是属于JVM的,有多少是属于JVM外的内存。

一个小技巧是对由于内存满而发生过Full GC的应用而言(不是主动调用System.gc的应用),多数情况下(例如由于产生的对象过大导致执行Full GC并抛出OutOfMemoryError的现象就要出外)可以认为其Java进程中显示出来的内存消耗值即为 JVM -Xmx的值 + 消耗的JVM外的内存值。

图-17

4.2.2.4pidstat

通过 pid也可以查看进程所消耗的内存量,命令格式为: pidstat -r -p [pid]  [interval] [times], 例如如下:

图-18

查看该进程所占用的物理内存RSS(Resident Set Size)和虚拟内存的大小VSZ(virtual memory size)。
从以上的几个工具来看,最佳的内存消耗分析方法是结合top或pidstat,以及JVM的内存分析工具来共同分析内存消耗状况。
下面通过例子分别展示Java应用对物理内存的消耗和对JVM堆内的消耗。

4.2.2 JVM堆外内存消耗分析

基于Direct ByteBuffer可以很容易地实现对物理内存的直接操作,而无须耗费JVM heap区。
例子中,为了更清晰地观察内存的变化情况,放入了多个Thread.sleep,加上 -Xms140 -Xmx140参数执行上面的代码,在执行过程中结合top命令和jstat命令查看java进程占用内存的大小以及JVM heap的变化情况。
[root@localhost ~]# jps
4661 jar
4670 Jps
[root@localhost ~]# pidstat -r -p 4661 1 100
jps找到java进程,jstat -gcutil [pid] 1000 10
代码示例:HighNonHeapOfMemoryDemo.java
结合上面top和jstat观察到的状态,可以查出 direct bytebuffer消耗的是JVM heap 外的物理内存。但它同样是基于GC方式来释放的,同时也可以看出JVM heap一旦使用后,即使进行了GC,进行中仍然会显示之前其所消耗的内存大小,因此JVM内存中具体的消耗状况必须通过JDK提供的命令才可以准确分析。
除了Direct Bytebuffer方式对JVM外物理内存的消耗外,创建线程也会消耗一定大小的内存。这一方面取决于-Xss对应值的大小,另一方面也取决于线程stack的深度,当线程退出时,其所占用的内存将自动释放。

4.2.3JVM堆内存消耗分析(需要重点补充)

Java应用中除了Direct Bytebuffer、创建线程等操作JVM外物理内存的方法外,大多数都是对于JVM heap区的消耗。

在Java程序出现内存消耗过多、GC频繁或OutOfMemoryError的情况后,要首先分析其所耗费的是JVM外的物理内存还是JVM heap区。 如为JVM外的物理内存,则要分析程序中线程的数量以及Direct Bytebuffer的使用情况;如果为JVM heap区,则要结合JDK提供的工具或外部的工具分析程序中的具体对象的内存占用情况。

4.3文件IO消耗分析

4.3.1 文件IO分析的常用命令

Linux在操作文件时,将数据放入文件缓存区,直到内存不够或系统要释放内存给用户进程使用时,才写入文件中。因此在查看Linux内存状况时经常会发现可用(free)的物理内存不多,但cached用了很多,这是Linux提升文件IO速度的一种做法,在这种做法下,如物理空闲内存够用,通常在Linux上只有写文件和第一次读取文件时会产生真正的文件IO. 在Linux中要跟踪线程的文件IO的消耗,主要方法是通过pidstat来查找。

4.3.1.1 pidstat

 输入如:pidstat -d -t -p [pid] 1 100 类似的命令即可查看线程的IO消耗状况,必须在2.6.20以上版本的内核或安装SYSSTAT工具后才会有效,执行后的效果如下图

图-18

其中kB_rd/s表示每秒读取的kB数,kB_wr/s表示每秒写入的kB数。

没有安装pidstat或内核版本不在2.6.20以后的版本的情况,可通过iostat来查看,但iostat只能查看整个系统的文件IO消耗情况,无法跟踪到进程的文件IO消耗情况。

4.3.1.2 iostat

直接输入iostat命令,可查看各个设备的IO历史状况,如下图:

图-19

在上面的几项指标中,其中Device表示设备卷名称或分区名;tps是每秒的IO请求数,这也是IO消耗情况中值得关注的数字;kB_read/s表示每秒钟读的kB数; kB_wrtn/s 表示每秒钟写的kB数; kB_read总共读取的数量; kB_wrtn总共写入的数量;

除了上面的方式外,还可通过输入iostat -x xvda 3 5  这样的方式来定时采样查看IO的消耗状况,执行如下:

图-20

其中值得关注的主要有: r/s表示每秒钟读的请求数;w/s表示每秒钟写的请求数;await表示平均每次IO操作的等待时间,单位为毫秒;avgqu-sz 表示等待请求的队列的平均长度;svctm表示平均每次设备执行IO操作的时间;%util表示有百分之多少用于IO操作。

在使用iostat查看IO的消耗时,首先要关注的是CPU中的iowait%所占的百分比,当iowait占据了主要的百分比时,就表示要关注IO方面的消耗状况了,

这时可以通过iostat -x 方式来详细地查看具体情况,参见下图:


图-21

4.3.2 文件IO消耗分析案例

当文件IO消耗过高时,对于Java应用最重要的是找到造成文件IO消耗高的代码,寻找的最佳方案为通过pidstat直接找到文件IO操作多的线程。之后结合jstack找到对应的Java代码,如没有pidstat,也可直接根据jstack得到的线程信息来分析其中文件IO操作较多的线程。
Java应用造成文件IO消耗严重主要是多个线程需要进行大量内容写入(例如频繁的日志写入)的动作;或磁盘设备本身的处理速度慢;或文件系统慢;或操作的文件本身已经很大造成的。
例子,通过往一个文件中不断地增加内容,文件越来越大,造成写速度慢,最终IOWait值高。代码实例见参考中附件:IOWaitHighDemo.jar

图-22

从图-22关注从top中的%iowait值偏高可以判断占据了很多CPU, 结合iostat的信息来看,主要是wkB/s比较高主要是写的消耗,并且花费在await上的时间要远大于svctm的时间。至于是什么动作导致了iowait,仍然需要对应用的线程dump来分析,找出其中的IO操作相关的动作。使用pidstat找到IO读写量大的线程ID,然后结合jstack生成的线程dump文件,即可找到相应的消耗文件IO多的动作。
jstack操作进程线程的dump文件如下:

图-23

从上面的线程堆栈中,可看到线程停留在了FileOutputStream.writeBytes(Native Method)这个Native方式上,这个方法所做的动作为将数据写入文件中,也就是所要寻找的IO操作相关的动作。继续跟踪堆栈往上查找,知道查找到系统中的代码,例如例子中的IOWaitHighDemo.java第57行,下图(图-24)是IOWaitHighDemo.java第57行相关代码

图-24

4.4 网络IO消耗分析

分布式系统,网络IO的消耗时非常值得关注的,尤其要注意网卡中断是不是均衡地分配到各CPU的(可通过 cat /proc/interrupts 查看的),见图-25

图-25

对于网络中断只分配到一个CPU的现象,google采用修改kernel的方法对网络中断分配不均的问题进行修复,据其测试性能大概能提升3x左右,或是采用支持MSI-X的网络来修复。

由于默认的Linux内核参数考虑的是最通用的场景,这明显不符合用于支持高并发访问的Web服务器的定义,所以需要修改Linux参数,使得Nginx等Web服务可以拥有更高的性能:
  首先,需要修改/etc/sysctl.conf来更改内核参数。如下最常用的配置
  fs.file-max = 999999
  net.ipv4.tcp_tw_reuse = 1
  net.ipv4.tcp_keepalive_time = 60
  net.ipv4.tcp_fin_timeout = 30
  net.ipv4.tcp_max_tw_buckets = 5000
  net.ipv4.ip_local_port_range = 1024 61000
  net.ipv4.tcp_rmem = 4096 32768 262142
  net.ipv4.tcp_wmem = 4096 32768 262142
  net.core.netdev_max_backlog = 8096
  net.core.rmem_default = 262144
  net.core.wmem_default = 262144
  net.core.rmem_max     = 2097152
  net.core.wmem_max     = 2097152
  net.ipv4.tcp_syncookies = 1
  net.ipv4.tcp_max_syn.backlog = 1024

  然后执行 sysctl -p 命令,使上述修改生效。
  上面的参数意义解释如下:
  (1) file-max:这个参数表示进程(比如一个work进程)可以同时打开的最大句柄数,这个参数直接限制最大并发连接数,需根据实际情况配置。
  (2) tcp_tw_reuse: 这个参数设置为1,表示允许将TIME-WAIT状态的socket重新用于新的TCP连接,这对于服务器来说很有意义,因为服务器上总会有大量TIME-WAIT状态。
  (3) tcp_keepalive_time: 这个参数表示当keepalive启用时,TCP发送keepalive消息的频率。默认是2小时,若将其设置的小一些,可以更快第清理无效的连接。
  (4) tcp_fin_timeout: 这个参数表示当服务器主动关闭连接时,socket保持在FIN-WAIT-2状态的最大时间。
  (5) tcp_max_tw_buckets: 这个参数表示操作系统允许TIME_WAIT套接字数量的最大值,如果超过这个数字,TIME_WAIT套接字将立刻被清除病打印警告信息。该参数默认为180000,过多的TIME_WAIT套接字会使Web服务器变慢。
  (6) tcp_max_syn_backlog:这个参数表示TCP三次握手建立阶段接收SYN请求队列的最大长度,默认为1024,将其设置得大一些可以使出现Nginx繁忙来不及accept新连接的情况时,Linux不至于丢失客户端发起的连接请求。
  (7) ip_local_port_range:这个参数定义了在UDP和TCP连接中本地(不包括连接的远端)端口的取值范围。
  (8) net.ipv4.tcp_rmem:这个参数定义了TCP接收缓存(用于TCP接收滑动窗口)的最小值、默认值、最大值。
  (9) net.ipv4.tcp_wmem:这个参数定义了TCP发送缓存(用于TCP发送滑动窗口)的最小值、默认值、最大值。
  (10)netdev_max_backlog:当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。这个参数表示该队列的最大值。
  (11)rmem_default:这个参数表示内核套接字接收缓存区默认的大小。
  (12)wmem_default:这个参数表示内核套接字发送缓存区默认的大小。
  (13)rmem_max: 这个参数表示内核套接字接收缓存区的最大大小。
  (14)wmem_max: 这个参数表示内核套接字发送缓存区的最大大小。
  (15)tcp_syncookies:该参数与性能无关,用于解决TCP的SYN攻击。
  参考自《Nginx模块开发与架构解析》中1.3.4linux内核参数的优化

4.4.1 网络IO分析的常用命令

在Linux中可采用sar分析网络IO的消耗状况,
sar 命令
例子:sar -n ALL     sar -n DEV
sar -n { <keyword> [,...] | ALL }
    Network statistics
    Keywords are:
    DEV     Network interfaces
    EDEV    Network interfaces (errors)
    NFS     NFS client
    NFSD    NFS server
    SOCK    Sockets (v4)
    IP      IP traffic      (v4)
    EIP     IP traffic      (v4) (errors)
    ICMP    ICMP traffic    (v4)
    EICMP   ICMP traffic    (v4) (errors)
    TCP     TCP traffic     (v4)
    ETCP    TCP traffic     (v4) (errors)
    UDP     UDP traffic     (v4)
    SOCK6   Sockets (v6)
    IP6     IP traffic      (v6)
    EIP6    IP traffic      (v6) (errors)
    ICMP6   ICMP traffic    (v6)
    EICMP6  ICMP traffic    (v6) (errors)
    UDP6    UDP traffic     (v6)

图-26

上面的输出的信息主要分三部分:
第一部分为网卡上成功接包和发包的信息,其报告中的信息主要有: rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
第二部分为网卡上失败的接包和发包的信息,其报告中的信息主要有:  rxerr/s   txerr/s    coll/s  rxdrop/s  txdrop/s  txcarr/s  rxfram/s  rxfifo/s  txfifo/s
第三部分为网卡上sockets的统计信息,其报告中的信息主要有:  rtotsck    tcpsck    udpsck    rawsck   ip-frag    tcp-tw
对于Java应用而言,使用的主要为tcpsck 和 udpsck
tcpsck:  Number of TCP sockets currently in use.
udpsck: Number of UDP sockets currently in use.
tcp-tw: Number of TCP sockets in TIME_WAIT state.
如需要详细跟踪TCP/IP通信的过程信息,则可通过tcpdump命令进行。
查看网络的常用命令:
netstat -ps
nfsstat
ethtool
snmp
ifport
ifconfig
route
arp
ping
traceroute
host
nslookup
ifconfig eth0
Linux下查看那些UDP类型端口开发,那些TCP端口开放
netstat -nupl  (UDP类型的端口)
netstat -ntpl  (TCP类型的端口)
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}';

4.4.2 网络IO消耗分析案例

由于没方法分析具体每个线程锁消耗的网络IO,  因此当网络IO消耗高时,对于Java应用而言只能对进程进行dump,查找产生了大量网络IO操作的线程。这些线程的特征是读取或写入网络流,在用Java实现网络通信时,通常要将对象序列化为字节流,进行发送,或读取字节流,并反序列化为对象。这个过程要消耗JVM堆内存,JVM堆内存大小通常有限。 网络容易出现问题地方如:数据库连接没有关闭(在抛出异常的情况下、网络经常出现抖动情况下)。 一端关闭另一端没有关闭等。运行程序见参考附件:NetUsedHighTCPServer.java
和 NetUsedHighTCPClient.java

图-27   显示   rxpck/s(Total number of packets received per second.)  txpck/s(Total number of packets transmitted per second.)   rxkB/s(Total number of kilobytes received per second.) txkB/s(Total number of kilobytes transmitted per second.) 都比较高

图-28

图-29

图-30

4.5程序执行慢原因分析

有些情况时资源消耗不多,但程序执行仍然慢,这种现象多出现于访问量不是非常大的情况下,造成这种现象的原因主要有以下三种:

4.5.1. 未充分使用硬件资源

当前主流机器CPU基本都是4或8核,如果程序中都是单线程串行的操作,并没有充分发挥硬件资源的作用,那么就可进行一定的优化(如:将串行修改为并发或并行的)来充分使用硬件资源,提升程序的执行速度。

如果在一定的负载情况下,多核CPU使用率不高,程序执行慢(通过记录执行的整个过程时间消耗或使用JProfiler等工具),找到执行耗时比例最大的代码。

4.5.2. 锁竞争激烈

 锁竞争激烈直接就会造成程序执行慢, 例如一个典型的例子是数据库连接池(底层也是一个网络连接池),通常数据库连接池提供的连接数都是有限的。假设提供的是10个,那么就意味着同时能够进行数据库操作的就只有10个线程,而如果此时有50个线程要进行数据库操作,那就会造成另外的40个线程处于等待状态,这种情况下对于8核类型的机器而言,CPU的消耗并不会高,但程序的执行仍然会较慢。

4.5.3. 数据量增长

数据量增长通常也是造成程序执行慢的典型原因,例如当数据库中单表的数据从100万条记录上涨到1个亿后,数据库的读写速度将大幅度下降,相应的操作此表的程序的执行速度也就下降了。
如果在一定的负载情况下,多核CPU使用率不高,程序执行慢(通过记录执行的整个过程的时间消耗或使用JProfiler等工具),找到执行耗时比例最大的代码。

5 性能调整(优)

在寻找到系统的性能瓶颈后,接下来的步骤就是调优,以提高系统性能,狭义的调优通常可以从硬件、操作系统、JVM以及应用程序四个方面入手,硬件和操作系统方面的知识不是本文章的重点,下面结合性能瓶颈的分析从JVM及程序方面来介绍一些常用的调优方法。

5.1 JVM调优

JVM调优主要是内存管理方面的调优包括 各个代的大小GC策略 等。由于GC动作会挂起应用线程,严重影响应用程序的性能,这些调优对于应用程序而言至关重要,根据应用程序的情况选择不同的内存管理策略有些时候能够大幅度地提升应用程序的性能,尤其是对于内存消耗较多的应用。下面就来看一些常用的内存管理调优的方法,这些方法都是为了尽量降低GC所导致的应用程序暂停时间。

5.1.1 代大小的调优

在不采用G1(G1不区分minor GC和Full GC)的情况下,通常minor GC会远快于Full GC, 各个代的大小设置直接决定了minor GC和Full GC触发的时机,在代大小的调优上,最关键的参数为:-Xms -Xmx -Xmn -XX:SurvivorRatio -XX:MaxTenuringThreshold  
-Xms和-Xmx通常设置为相同的值,避免运行时要不断地扩展JVM内存空间,这个值决定了JVM Heap所能使用的最大空间。
-Xmn决定了新生代(New Generation)空间的大小,新生代中Eden、S0 和 S1 三个区域的比率可通过-XX:SurvivorRatio 来控制。
-XX:MaxTenuringThreshold 控制对象在经历多少次Minor GC后才转入旧生代,通常又将此值称为新生代存活周期,此参数只有在串行GC时有效,其他GC方式时则由Sun JDK自行决定。
介绍Minor GC 和 Full GC触发的时机??,在此处就直接举例来看看不同的代大小设置情况下,应用耗费在GC上的时间,从中也可看出在不同的场景下代大小调优的方法。

5.1.1.1. 避免新生代大小 设置过小

当新生代大小设置过小时会产生两种比较明显的现象: 一是minor GC的次数更加频繁;二是有可能导致minor GC对象直接进入旧时代,此时如进入旧生代的对象占据了旧生代剩余空间,则触发Full GC.

运行示例程序(示例程序GCDemo.java在参考部分下载)

首先以JVM启动参数:-Xms135M -Xmx135M -Xmn10M -XX:+UseSerialGC执行上面的代码,通过jstat跟踪到的GC状况为:

图-31          

其次,按照这样的思路调大新生代到30MB,则JVM启动参数为:-Xms135M -Xmx135M -Xmn50M -XX:+UseSerialGC执行上面的代码,通过jstat跟踪到的GC状况为:                  

图-32

通过对比发现,当配置-Xmn10M与-Xmn50M,GCT耗费的时间从0.403秒降到0.237秒。 

除了调大新生代大小外,如果能够调大JVM Heap的大小,通常意味着单次GC时间的增加。

当minor GC过于频繁,或发现经常出现minor GC时,Survivor的一个区域空间满,且Old Gen增长超过了Survivor区域大小时,就需要考虑新生代大小的调整了。调整时的原则是在不能调大JVM Heap的情况下,尽可能放大新生代空间,尽量让对象在minorGC阶段被回收,但新生代空间也不可过大;在能够调大JVM Heap的情况下,则可以按照增加的新生代空间大小增加JVM Heap大小,以保证旧生代空间够用。

5.1.1.2. 避免新生代大小 设置过大

新生代设置过大会带来两个典型的现象,一是旧生代变小了,有可能导致Full GC频繁执行; 二是minor GC的耗时大幅度增加。

仍然用上面的例子 首先以JVM启动参数:-Xms135M -Xmx135M -Xmn105M -XX:+UseSerialGC执行上面的代码,通过jstat跟踪到的GC状况为:

首先以JVM启动参数:-Xms135M -Xmx135M -Xmn105M -XX:+UseSerialGC 执行上面的代码,通过jstat跟踪到的GC状况为:                

图-33     

其次以JVM启动参数:-Xms135M -Xmx135M -Xmn25M -XX:+UseSerialGC 执行上面的代码,通过jstat跟踪到的GC状况为:

图-34   

从这个调整和之前把新生代调为25MB时对比,此时minor GC下降了,但Full GC仍然多了一次。原因在于,当第二次到达minor GC的触发条件时,JVM基于悲观原则,判断目前old区的剩余空间小于可能会从新生代晋升到old区的对象的大小,于是执行了Full GC,从而minor GC消耗的时间来看,单词minor GC的时间也比以前慢了不少。

从上面的分析来看,可见新生代通常不能设置得过大,大多数场景下都应设置的比旧生代小,通常推荐的比例是新生代占JVM Heap区大小的33%左右。

5.1.1.3. 避免Survivor区设置过小或过大

在采用串行GC时,默认情况下Eden、S0、s1的大小比例为 8:1,调整为以下参数执行上面的示例代码:

java -Xms135M -Xmx135M -Xmn20M  -XX:SurvivorRatio=10  -XX:+UseSerialGC  -jar GCDemo.jar ,通过jstat -gc [pid] 1000 50  观察到其GC状况为:

图-35

从上面的分析来看,在无法调整JVM Heap以及新生代的大小时,合理调整Survivor区的大小也能带来一些效果。

当调大SurvivorRatio值意味着Eden区域变大,minor GC的触发次数会降低,但此时Survivor区域的空间变小了,如有超过Survivor空间大小的对象在minor GC后仍没有被回收,则会直接进入旧生代;

当调小SurvivorRatio则意味着Eden区域变小,minor GC的触发次数会增加,Survivor区域变大,意味着可以存储更多在minor GC后仍存活的对象,避免其进入旧生代。

5.1.1. 4. 合理设置新生代存活周期

新生代存活周期的值决定了新生代的对象经过多少次Minor GC后进入旧生代,因此这个值也需要根据应用的状况来做针对性的调优,JVM参数上这个值对应的为-XX:MaxTenuringThreshold,默认值为15次, 下面通过设置参数的例子(参考部分附件)看一下:

首先 -Xms150M -Xmx150M -Xmn20M -XX:+UseSerialGC执行代码,执行结果如下 

图-36

其次 -Xms150M -Xmx150M -Xmn20M -XX:+UseSerialGC -XX:MaxTenuringThreshold=20 执行代码,执行结果如下:

图-37

从上面的调整结果可见,在增大了存活周期后,对象在Minor GC阶段被回收的机会就增加了,但同时带来的是survivor区被占用,但此值仅在串行GC和ParNew GC时可调整。

总结上面的几个例子来看,对于代大小的调优,主要是合理调整 -Xms、-Xmx、-Xmn 以及 -XX:SurvivorRatio的值,尽可能减少GC所占用的时间。

-Xms、-Xmx适用于调整整个JVM Heap区大小,在内存不够用的情况下可适当加大此值,这个值能调整到多大 取决于操作系统位数 以及 CPU的能力。

-Xmn 适用于调整新生代的大小、新生代的大小决定了多少比例的对象有机会再minor GC阶段被回收,但此值相应的也决定了旧生代的大小。新生代越大,通常意味着多数对象能够在minor GC阶段被回收掉,但同时意味着旧生代的空间会变小,可能会造成更频繁的Full GC,甚至是OutOfMemoryError.

 -XX:SurvivorRatio 适用于调整Eden区和Survivor区的大小,Eden区越大通常也就意味着minor GC发生的频率越低。但有可能会造成Survivor区太小,导致对象在经过minor后直接就进入旧生代了,从而更频繁的触发Full GC, 这取决于当Eden区满的时候其中存活对象的比例。

在清楚掌握minor GC、Full GC的触发时机以及代大小的调整后,结合应用的状况(例如创建出的对象都可很快被回收掉、缓存对象多等)通常就可较好设置代的大小,减少GC锁占用的时间。在调整后可结合jstat、VisualVM等查看GC的变化是否达到了调优的目的。

5.1.2 GC策略的调优

Sun JDK所提供的几种GC策略,串行GC性能太差,因此在实际场景中使用的主要为并行和并发GC,

通过下面这个例子触发多次GC,查看并行GC以及并发GC时对于应用造成的不同的暂停时间。

首先以VM Args: -Xms680M -Xmx680M -Xmn80M -XX:+UseConcMarkSweepGC -XX:+PrintGCApplicationStoppedTime -XX:+UseCMSCompactAtFullCollection -XX:CMSMaxAbortablePrecleanTime=5 参数执行,通过jstat观察到的GC状况:           

图-38

其次以VM Args: -Xms680M -Xmx680M -Xmn80M -XX:+PrintGCApplicationStoppedTime -XX:+UseParallelGC 参数执行,通过jstat观察到的GC状况:

图-39

大部分大部分 Web应用在处理请求时设置了一个最大可同时处理的请求数,当超过次请求数时,会将之后的请求放入等待队列中,而这个等待队列也限制了大小。当等待队列慢了以后仍然有请求进入,那么这些请求将会直接被丢弃,所有的请求又都是有超时限制的。在这种情况下如触发了造成应用暂停时间较长的Full GC,那么有可能在这次Full GC后,造成了3秒的暂停,那么之前在此应用上等待处理的请求就会全部超时了。从上可看出,Web应用非常需要一个对应用造成暂停时间短的GC,再加上大部分Web应用的瓶颈都不在CPU上。因此对于Web应用而言,在G1还不够成熟的情况下,CMS
GC是不错的选择。

5.1.3 JVM调优案例

以下为一个系统一段时间内的GC状况,此系统运行的机器操作系统为32为,CPU为4核,物理内存为4G,启动参数为:
VM args: -server -Xms1536m -Xmx1536m -Xmn700m -XX:PermSize=96m -XX:MaxPermSize=96m
根据启动的参数以及机器配置来看,目前系统新生代GC的类型为Parallel Scavenge,启动时Eden Space,S0,S1将会由HotSpot自动调整,大致空间大小:Eden561、59和72这样的分配情况,假设多数情况下均为这样的占比,目前的运行状况为每次Minor GC后大概有16MB对象进入旧生代,假设这些对象都是因为超过Survivor Space才进入旧生代的,那么可以认为每次Minor GC时有16+72=88MB的对象时存活的,从目前Full GC的状况来看,其实这88MB的对象在一段时间后都是可以回收的,那么理论上来说只用将Survivor
Space扩大到88MB以上即可,按着这个想法,可以将启动参数调整为:
VM args: -server -Xms1536m -Xmx1536m -Xmn700m -XX:PermSize=96m -XX:MaxPermSize=96m -XX:InitialSurvivorRatio=7 -XX:-UseAdaptiveSizePolicy
除了以上方法外,还可采用的另一方式为将GC测量调整为CMS GC,参数如下:
VM args: -server -Xms1536m -Xmx1536m -Xmn700m -XX:PermSize=96m -XX:MaxPermSize=96m
  -XX:+UseConcMarkSweepGC -XX:+UseCompactAtFullCollection
  -XX:CMSMaxAbortablePrecleanTime=500 -XX:+CMSPermGenSweepingEnabled
  -XX:+CMSClassUnloadingEnabled
在做完以上的调优后,都需要继续结合jstat工具来查看是否达到了调优的目标,如果没有则需要继续按着以上步骤进行参数的调整。具体参数列表可以参考Oracle官方
在进行参数调整时,可根据目前收集到的顶峰时系统请求次数、响应时间以及GC的信息,来估计系统每次请求需要消耗的内存,以及每次Minor GC时存活的对象所占的内存,从而估计需要设置多大的Survivor才能够尽可能地避免对象进入旧生代。
例如:每秒的请求为60次,GC信息显示每10秒执行一次minor GC,每次Minor GC会有10MB对象转入旧生代,每次Minor GC在Eden分配的内存为600MB,Survivor为100MB.根据这些信息,可以简单认为系统中每次请求消耗的内存大致为1MB, 并粗略估计为在开始Minor GC时,还有110个请求未处理完。    对于这样的状况,简单的调优方式可以在保持Eden Space 600 MB的情况下, 将Survivor Space增长到120MB,那就基本可以做到在当前的响应速度下,如10秒内接受的请求最多为600个时,Minor
GC时大部分情况不会有对象转入旧生代,但毕竟系统中的请求响应时间、内存消耗分布不会这么平均,并且还回出现直接在旧生代分配的现象,因此通常按这样粗略的估计设置的参数仍然会达不到目标,要继续进行一些细微的调节来逐步达到目标。
由于参数的估计是以请求次数和响应时间为基准的,因此一旦系统的响应速度下降或请求的次数上升,就可能仍然会导致大量对象进入旧生代,从而触发频繁的Full GC,频繁的Full GC又导致系统的响应速度下降,从这个层面来看,根本上需要做的调优仍然是提升请求的处理速度以及降低每次请求需要分配的内存,只有这样才能使的应用能够支撑更高的并发量,否则就会随着并发量的上涨而迅速出现瓶颈。
旧生代大小的调整一方面要依据新生代的大小,另一方面要依据系统中持久存活的对象会消耗多大的内存来决定。
如系统不是CPU密集型,且从新生代进入旧生代的大部分对象时可回收的,那么采用CMS GC可以更好地在旧生代满之前完成对象的回收,更大程度降低了Full GC发生的可能。
目前内存管理方面,JVM自身已经做得非常不错了,因此如果不是有确切的GC造成性能低的理由,就没有必要做过多的细节方面的调优(例如:survivor区大小的设置等)。多数情况下只须选择GC策略并设置JVM Heap的大小即可。在调整了内存管理方面的参数后应通过-XX:+PrintGCDetails、-XX:+PrintGCTimeStamps、-XX:+PrintGCApplicationStoppedTime 及jstat 或 visualvm等方式来观察调整后GC的状况,除内存管理方面的调优外,Sun JDK还提供了一些其他方面的调优参数:
如:-XX:CompileThreshold、 -XX:+UseFastAccessorMethods 及 -XX:+UseBiasedLocking等。
基本上JVM调优,多数请下只需要选择GC策略(串行、并行、并发、G1)并设置JVM Heap的大小即可。在调整了内存管理方面的参数应通过-XX:+PrintGCDetails、-XX:+PrintGCTimeStamps、-XX:+PrintGCApplicationStoppedTime及jstat 或 visualvm等方式来观察调整后GC的情况,除了内存管理方面的调优外, Sun JDK还提供了一些其他方面的调优参数:如-XX:CompileThreshold、-XX:+UseFastAccessorMethods
及-XX:+UseBiasedLocking等。
除了以上基于对JDK实现及JDK调优参数的掌握进行的调优外,关注JDK的新版本也是不错的选择。每次JDK新版本的发布几乎都会在性能上做出一些优化,也许这些优化正是应用所需要的额。

5.2 程序调优

5.2.1 CPU消耗严重的解决方法

5.2.1.1. CPU us高的解决方法       

根据之前的分析,CPU us高的原因主要是执行线程无任何挂起动作,且一直执行,导致CPU没有机会去调度执行其他的线程,造成线程饿死的现象。对于这种情况,常见的一种优化方法是对这种线程的动作增加Thread.sleep(),以释放CPU的执行权,降低CPU的消耗。解决示例:UsHighOfCpuSolveDemo.java

按照这样的思路,对"CPU消耗分析"中的例子进行修改,在往集合中增加元素的部分增加sleep,修改如下代码:

图-40

 重新执行以上代码,通过top查看效果如图执行效果:

图-41

从上结果可见,CPU的消耗大幅度下降,当然,这种修改方式是以损失单次执行性能为代价的,但由于降低了CPU的消耗,对于多线程的应用而言,反而提高了总体的平均性能。
在实际的Java应用中会有很多类似的场景,例如多线程的任务执行管理器,它通常要通过扫描任务集合列表来执行任务。对于这些类似场景,都可通过增加一定的sleep时间来避免消耗过多的CPU.
对于其他类似循环次数太多、正则、计算等造成的CPU us过高的情况,则要结合业务需求来进行调优。
对于GC频繁造成的CPU us高的现象,则要通过JVM调优或程序调优,降低GC的执行次数。

5.2.1 2. CPU sy高的解决方法

5.2.1.2.1 减少线程数   

CPU sy高的原因主要是线程的运行状态要经常切换,对于这种情况,最简单的优化方法是减少线程数。

可见减少线程数时能让sy值下降的,所以不是线程数越多吞吐量就越高,线程数需要设置为合理的值,这需要根据应用情况来具体决定,同时使用线程池避免要不断地创建线程。如应用要支撑大量的并发,在减少线程数的情况下最好是增加一个缓冲队列,避免因为线程数的减少造成系统出错率上升。

造成CPU sy高的原因除了启动的线程过多以外,还有一个重要的原因是程序之间锁竞争激烈,造成了线程状态经常要切换,因此尽可能降低线程间的锁竞争也是常见的优化方法。锁竞争降低后,线程的状态切换的次数也就会下降,sy值回相应下降。但值的注意的是人线程数过多,调优后有可能会造成us值过高,所以合理地设置线程数非常关键。锁竞争更有可能造成系统资源消耗不多,但系统性能不足的现象。

5.1.2.2.2 Coroutine(协程)

除了以上两种情况外,对于分布式Java应用而言,还有一种典型现象是应用中有较多的网络IO操作或确实需要一些锁竞争机制(例如数据库连接池),但为了能够支持高的并发量,在Java应用中有只能借助启动更多的线程来支撑。在这样的情况下当并发量增长到一定程度后,可能会造成CPU sy高的线程,对于这种现象,可采用协程(Coroutine)来支撑更高的并发量,避免并发量上涨后造成CPU sy消耗严重、系统load迅速上涨和系统性能下降。
采用协程后,能做到当线程等待数据库执行结果时,就立刻释放此线程资源给其他请求,等到数据库执行结果返回后才继续执行,在Java中目前主要可用于实现协程的框架为Kilim. 在使用Kilim执行ixiang任务时,并不创建Thread,而是改为创建Task,Task相对于Thread而言就轻量级多了。当次Task要做阻塞动作时,可通过Mailbox.get 或 Task.pause来阻塞当前的Task,Kilim会保存Task之后执行需要的对象信息,并释放Task执行所占用的线程资源;当Task的阻塞动作完成或被唤醒时,此时Kilim会重新载入Task所需的对象信息,恢复Task的执行,相当于Kilim来承担了线程的调度以及上下文切换动作。
这种方式相对原生Thread方式更为轻量,且能够更好第利用CPU,因此可做到仅启动CPU核数的线程数,以及大量的Task来吃撑高并发量,Kilim带来的是线程使用率的提升,但同时由于要在JVM堆中保存Task上下文信息,因此在采用Kilim的情况下要消耗更多的内存。
下面是一个传统方式的例子:SimpleBenchMark.java 和基于Kilim采用Coroutine方式支撑高并发请求例子:SimpleBenchMarkV2.java
通过运行比较传统方式耗时大概为3077ms,而基于Kilim采用协程方式的耗时大概为277ms,可见在这种高并发的情况下,协程方式对性能提升以及支撑更高并发量可以起到很大的作用。
目前Kilim还比较新,没有商用成功的案例,如打算在实际的系统中使用,还需要谨慎。一方面是0.7版本中基于object.wait/notify机制实现的Scheduler在高压下会出现bug,可自行基于ThreadPoolExecutor进行改造;另一个方面Mailbox.get(timeout)是基于Timer实现的,由于Timer在增加task到队列时和运行task队列是互斥的(即使是ScheduledTheadPoolExecutor也同样需要锁整个队列),对于大并发的应用而言这里是个潜在的瓶颈。对于Java应用而言,Timer是一个经常用来实现定时任务的类,但Timer的性能在高并发下一般,感兴趣的读者可以尝试基于TimerWheel算法来提升Timer的性能。
现在要在Java应用中使用Kilim来实现协程方式并不简单,因为协程方式要求所有的操作都不阻塞原生线程,这就要求应用中不能使用目前Java里的同步、锁等机制。除了这些外,还需要解决同步访问数据库、操作文件等问题,这些都必须改为是异步方式或Kilim中的Task暂停的机制。
目前Sun JDK7中也有一个支持协程方式的实现http://code.google.com/p/coroutines/。
另外基于JVM的Scala的Actor也可用于在Java中使用协程。
除了软件方面对提升CPU使用率做出的努力外,硬件方面的CPU专业化(例如 GPU进行图形计算)也很值的关注。

5.2.2 内存消耗严重的解决方法

在内存消耗方面,最明显的在于消耗了过多的JVM Heap内存,造成GC频繁执行的现象,而物理内存方面的消耗通常来说不会成为Java应用中的主要问题。除了JVM的调优外,在寻找到内存消耗严重的代码后,可从代码本身进行优化,避免内存资源消耗过多。此处就介绍一些JVM Heap内存消耗严重时常用的程序调试方法。

5.2.2.1. 释放不必要的引用

内存消耗最严重的情况中最典型的一种现象是代码中持有了不需要的对象引用,造成这些对象无法被GC,从而占据了JVM堆内存。这种情况最典型的一个例子是在复用线程的情况下使用ThreadLocal,由于线程复用,ThreadLocal中存放的对象如未做主动释放的话则不会被GC释放。示例如下:

执行代码,通过jstat观察,会发现在Old Generation JVM内存一直被使用了60MB左右。

执行代码,通过jstat观察,会发现在Old Generation JVM内存一直被使用了60MB左右。对于这种情况,要注意在线程内的动作执行完毕时执行ThreadLocal.set把对象清除,避免持有不必要的对象引用。会发现在Old Generation JVM内存从60MB左右降到了1M左右。

5.2.2.2. 使用对象缓存池

创建对象的实例要耗费一定的CPU以及内存,使用对象缓存池一定程度上可降低JVM Heap内存的使用。
通过运行程序:ObjectPoolDemo.java(下载地址见 参考部分)
1 没有采用对象缓存池情况
参数:VM Args : -Xms128M -Xmx128M -Xmn64M   ( runMode is NOTUSE_OBJECTPOOL:)
            Main Args:
执行结果:Execute summary: Round( 10 ) Thread Per Round( 100 ) Object Factor ( 10 ) Execute Time ( 111594 ) ms
2 采用对象缓存池情况
参数:VM Args : -Xms128M -Xmx128M -Xmn64M   ( runMode is USE_OBJECTPOOL:)
            Main Args: 2
执行结果:Execute summary: Round( 10 ) Thread Per Round( 100 ) Object Factor ( 10 ) Execute Time ( 1094 ) ms
从上面的结果对比可看出,在内存消耗严重的情况下,采用对象缓存池可大幅度提升性能,避免创建对象所耗费的时间及频繁GC造成的消耗。

5.2.2.3. 采用合理的缓存失效算法

上面说到了采用对象缓冲池来降低内存的消耗,但如果放入太多的对象在缓存池中,反而会造成内存的严重消耗。同时由于缓存池一直对这些对象持有引用,从而会造成Full GC增多,对于这种情况要合理控制缓存池的大小。

 控制缓存池大小的问题在于当到达缓存池的最大容量后,如果要加入新的对象该如何处理?有一些经典的缓存失效算法来清除缓冲池中的对象,例如FIFO,LRU,LFU等。采用这些算法可控制缓存池中的对象数目,避免缓存池中的对象数量无限上涨。

ObjectCachePoolDemo.java(下载地址见 参考部分)

输入参数:向容器总插入15个元素,保存容器10个元素

输出结果:

size(10), policy(1) FIFO
6, 6
7, 7
8, 8
9, 9
10, 10
11, 11
12, 12
13, 13
14, 14
15, 15

分析:按着FIFO缓存失效算法,失效了元素1,2,3,4,5,保持容器中有10个元素

5.2.2.4. 合理使用SoftReference 和 WeakReference

对于占据内存但又不是必须存在的对象,例如缓存对象,也可以基于SoftReference或WeakReference的方式来进行缓存。SoftReference的对象会在内存不够用的时候进行回收,WeakReference的对象则会在Full GC的时候回收,采用这两种方式也能一定程序上减少JVM Heap区内存的消耗。

对于以上硬件资源消耗过多造成的性能不足的现象,除了软件方面调优外,在大多数情况下还可通过升级或增加硬件来提升程序的性能。

5.2.2 文件IO消耗严重的解决方法

从程序角度而言,造成文件IO消耗严重的原因主要是多个线程在写大量的数据到同一个文件,导致文件很快变的很大,从而写入速度越来越慢,并造成各线程激烈抢文件锁,对于这类情况,通常调优的有以下方法:可以参考log4j

5.2.2.1 异步写文件

将写文件的同步动作改为异步动作,避免应用由于写文件慢而性能下降太多,例如写日志,可以使用log4j提供的AsyncAppender.

5.2.2.2 批量读写

频繁的读写操作对IO消耗会很严重,批量操作将大幅度提升IO操作的性能。

5.2.2.3 限流

频繁读写的另外一个调优方式是限流,从而将文件IO消耗控制到一个能接受的范围。log.error  log.warn. log.info. log.debug.

5.2.2.4 限制文件大小

操作太大的文件也是造成文件IO效率低的一个原因,因此对于每个输出的文件,都应该做大小的限制,在超过最大值后可生成一个新的文件,类似log4j中RollingFileAppender的maxFileSize属性的作用。

除了以上这些外,还有就是尽可能采用缓冲区等方式来读取文件内容,避免不断与操作系统交行,参考 Sun官方的关于Java文件IO优化的文章:见参考部分的[4.5]Articles: Tuning Java I/O Performance

5.2.3 网络IO消耗严重的解决方法

从程序角度而言,造成网络IO消耗严重的原因主要是同时需要发送或接收的包太多。对于这类情况,常用的调优方法为进行限流,限流通常是限制发送packet的频率,从而在网络IO消耗可接受的情况下来发送packet. 《Linux Performance and Tuning Guidelines.pdf》《IO的阻塞与非阻塞、同步与异步以及Java网络IO交互方式》

5.3 对于资源消耗不多,但程序执行慢的情况(需补充) 

对于分布式Java应用而言,造成这这种情况的主要原因通常有锁竞争激烈及未充分发挥硬件资源两种。

5.3.1 锁竞争激烈

线程多了后,锁竞争的状况会比较明显,这时线程很容器处于等待锁的状况,从而导致性能下降以及CPU sy上升。
示例:LockHotDemo.java
执行结果:Execute summary: Round( 10 ) Thread Per Round( 200 ) Execute Time ( 10579 ) ms
此时各方面的资源消耗并不高,但性能比没哟锁的情况下降了非常多。从这个例子可见,
锁是影响性能的重要因素,但为了保证资源的一致性,多线程应用中锁的使用是不可避免的,只能尽量去降低线程间的锁竞争,
常见的方法如下:使用并发包中的类、使用Treiber算法、使用Michael-Scott非阻塞队列算法、尽可能减少锁、拆分锁、去除读写操作的互斥锁等

5.3.1.1 使用并发包中的类

第三方并发库:Disruptor、JPPF、Kilim、Amino

对于java.util.concurrent包中的类的分析,可以看出,并发包中的类多数都采用了 lock-free、nonblocking算法,减少了多线程情况下资源的锁竞争,因此对于线程间要共享操作的资源而言,应尽量使用并发包中的类来实现(AtomicInteger、ConcurrentHashMap等),当系统提供的并发包中的类无法满足需求时,可参考学习一些nonblocking算法来自行实现,nonblocking算法的机制,为基于CAS来做到无需lock就可实现资源一致性的保证,主要的实现nonblocking的算法有:

5.3.1.2 使用Treiber算法

Treiber算法主要用于实现Stack,基于Treiber算法实现的无阻塞的Stack代码如下:

代码例子参考:并发栈ConcurrentStack<E>: http://blog.csdn.net/ajian005/article/details/18324407

由于Stack是LIFO方式,因此不能采用类似LinkedBlockingQueue中两把锁的机制。这里巧妙地采用AtomicReference来实现了无阻塞的push和pop, 在push时基于AtomicReference的CAS方法来比较目前的head是否一致。如不一致,说明有其他线程改动了,如有改动则继续循环,直到一致,才修改head元素,在pop时可以采用同样的方式进行操作。

测试用例,测试结果:

Stack consume Time:  125 ms
ConcurrentStack consume Time:  63 ms

5.3.1.3 使用Michael-Scott非阻塞队列算法

  和Treiber算类似,Michael-Scott算法也是基于CAS以及AtomicReference来实现队列的非阻塞操作,java.util.concurrent中的ConcurrentLinkedQueue就是典型的基于Michael-Scott实现的非阻塞队列。ConcurrentLinkedQueue在执行offer动作时,通过CAS比较拿到的tail元素是否为当前处于末尾的元素,如不是则继续循环,如是则将tail元素更新为新的元素。

在执行poll动作时,通过CAS比较拿到的head元素是否为当前处于首位的元素,如不是则继续循环,如是则将head后的元素赋值给head,同时获取之前head元素中的值并返回。

从上面两种算法来看,基于CAS 和 AtomicReference来实现无阻塞是不错的选择。但值得注意的是,由于CAS是基于不断的循环比较来保证资源一致性的,对于冲突较多的应用场景而言,CAS会带来更高的CPU消耗,因此不一定采用CAS实现无阻塞的就一定比采用Lock方式的性能好。业界中还有一些无阻塞算法的改进,例如MCAS、WSTM等。

5.3.1.4 尽可能少用锁

尽可能让锁仅在需要的地方出现,通常没必要对整个方法加锁,而只对需要控制的资源做加锁操作。

  将锁最小化后,性能会有提高,在编写多线程程序时,要仔细考虑哪些地方是要加锁的,哪些地方是不要加锁的。尽可能让锁最小化,只对互斥及原子操作的地方加锁,加锁时尽可能以被包含资源的最小粒度为单位。

5.3.1.5 拆分锁

   拆分锁即把独占锁拆分为多把锁,常见的有读写锁拆分及类似ConcurrentHashMap中默认拆分为16把锁的方法。拆分锁很大程度上能提高读写的速度,但需要注意的是在采用拆分锁后,全局性质的操作会变的比较复杂,例如ConcurrentHashMap中的size操作。

 是否拆分锁还得根据业务场景来决定。有些场景并不适合做锁的拆分,而且锁拆分的太多也会造成其他副作用,例如CPU的消耗明显增加等,因此锁拆分要在合理的业务场景以及CPU消耗下进行。

5.1.3.6 去除读写操作的互斥锁

  在修改时加锁,并在复制对象时进行修改,修改完毕后切换对象的引用,而读取时则不加锁,这种方式称为CopyOnWrite. CopyOnWriteArrayList是CopyOnWrite方法的典型实现,CopyOnWrite的好处是可以明显提升读的性能,对于读多写少的应用非常适合,但由于写操作时每次都要复制一份对象,会造成更多的内存消耗。

5.3.2 未充分使用硬件资源

这种情况也是性能低的应用中经常出现的,主要体现在未充分使用CPU 和 内存。

5.3.2.1 未充分使用CPU

对于Java应用而言,未充分使用CPU的原因主要是在能并行处理的场景中未使用足够的线程。

例子 CpuNotUseEffectiveDemo.java 单线程

对于有多个或多核的CPU而言要充分使用CPU.

 例子 CpuUseEffectiveDemo.java 单线程

对于此类可以演变为多线程也无须加锁的场景而言,启动多个线程后的性能会远高于单线程,并且只要启动的线程数合理,也不会给CPU造成过大的负担。如从单线程演变为多线程要加锁,则要引入尽量减少锁竞争的方法,并进行性能测试以保证调优后的资源消耗以及性能满足要求。

 此种类型的场景还有不少,例如单线程的计算,可以拆分为多线程来分别计算,最后将结果合并,这样的方式也可很大程度提升系统的性能,JDK7中的fork-join框架可以给以上这类场景提供一个好的支撑方法。

在CPU资源消耗可接受,且不会因为线程增加带来激烈锁竞争的场景下,应适当对处理过程进行分解,增加线程数从而能并行处理以提升系统的运行性能。但在重构为并行时,要注意控制内存消耗,而且通过重构为并行能提升的性能也是有限的。著名的Amdahl定律中的简单计算公式为:

系统提升的性能 = 1/ (F+(1-F)/N), 其中F为必须串行化的执行在整个执行过程中所占的比率, N为处理器个数,例如在整个执行过程中串行化的过程占50%,那么最多只能提升2倍的性能。

5.3.2.2  未充分使用内存

   未充分使用内存的场景非常多,如数据的缓存、耗时资源的缓存(例如数据库连接的创建、网络连接的创建等)、页面片段的缓存等,这样的场景比较容易理解,毕竟内存的读取肯定远快于硬盘、网络的读取。但也要避免内存资源的过度使用,在内存资源消耗可接受,GC频率及系统结构(例如集群环境可能会带来缓存的同步等)可接受的情况下,应充分使用内存来缓存数据,提升系统的性能。

5.3.2.3  对于数据量大造成的性能不足

    数据分库、分表、分区等

好的调优策略应是收益比(调优后提升的效果/调优改动所付出的代价)最高的,通常来说功能简单的系统调优比较好做,否则有可能出现调优了当前功能影响到了其他功能,因此应尽量保持单机上应用功能的纯粹性,这是大型系统的基本架构原则。

从纯粹的软件调优角度来讲,充分而不过分使用硬件资源,合理调整JVM(操作系统、虚拟机)以及合理使用JDK包(语言提供的核心库)是调优的三大有效原则,调优没有“银弹”,结合系统现状和多尝试不同的调优策略是找到合适的调优方法的唯一途径。

查看是否有进程被系统给kill掉了:
  dmesg | grep  -i oom
  cat /var/log/messages
  sudo grep oom /var/log/messages

  sudo less /var/log/messages

6参考及扩展阅读资料

6.1 JVM相关:

[6.1.1] 《Java虚拟机规范(Java SE 7版)》《The Java Virtual Machine Specification, Java SE 7 Edition》
[6.1.2] JAVA启动参数{标准参数(-) 非标准参数(-X)非Stable参数(-XX)} :http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/java.html
[6.1.3] JAVA启动参数整理http://blog.csdn.net/turkeyzhou/article/details/7619472
[6.1.4] Java 6 JVM参数选项大全(中文版):http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm
[6.1.5] Java 6 JVM参数选项大全(英文版)| Java HotSpot VM Options:http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html
[6.1.6] VM非稳态参数(Unstable Options,即以-XX:开头的参数),使用-XX:+PrintFlagsFinal参数可以输出所有参数的名称及默认值。
[6.1.7] Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning : http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html
[6.1.8]Articles: Tuning Java I/O Performance:http://www.oracle.com/technetwork/articles/javase/perftuning-137844.html
[6.1.9] Monitoring and Managing Java SE 6 Platform Applications:http://www.oracle.com/technetwork/articles/javase/monitoring-141801.html
[6.1.10]《分布式Java应用:基础与实践》
[6.1.11]《分布式Java应用:基础与实践》样章、代码、纠错、补充 http://bluedavy.me/?p=64
[6.1.12]《分布式Java应用:基础与实践》扩展阅读链接:http://blog.csdn.net/yang_net/article/details/5853678
[6.1.13]《Java程序性能优化:让你的Java程序更快、更稳定》
[6.1.14]《分布式Java应用:基础与实践》
[6.1.15]《深入理解Java虚拟机:JVM高级特性与最佳实践》
[6.1.16] 性能调优概述:http://www.blogjava.net/BlueDavy/archive/2008/06/29/211558.html
[6.1.17] Java性能调优笔记 :http://blog.csdn.net/yang_net/article/details/5830820
[6.1.18] 性能调优之我见:http://softtest.chinaitlab.com/bug/878841.html
[6.1.19] 系统性能调优吐血总结分享:http://www.cnblogs.com/wcyao/archive/2011/06/29/2091962.html
[6.1.20] 关注性能: 调优垃圾收集 http://www.ibm.com/developerworks/cn/java/j-perf06304/index.html
[6.1.21]Java 理论与实践: 垃圾收集简史:http://www.ibm.com/developerworks/cn/java/j-jtp10283/
[6.1.22] kilim: http://www.malhar.net/sriram/kilim/
[6.1.23] Java 开发 2.0: Kilim 简介 : http://www.ibm.com/developerworks/cn/java/j-javadev2-7.html
[6.1.24] 分析jmap的dump文件: Eclipse Memory Analyzer    http://www.eclipse.org/mat/downloads.php

扩展阅读:

[6.1.25]2 Hyper Threading
[6.1.33]10 《深入理解计算机系统 Computer System A Programmer's Perspective》
[6.1.34]11 JRockit中可以选择是采用原生的线程或模拟的线程

[6.1.45]22 http://en.wikipedia.org/wiki/Amdahl's_law
[6.1.46] Kilim实现浅析(一)http://www.blogjava.net/killme2008/archive/2010/09/17/332281.html
[6.1.47]coroutines (Java协程库):http://code.google.com/p/coroutines/
[6.1.48]Java SE 6 Performance White Paper:http://www.oracle.com/us/technologies/java/6-performance-137236.html
[6.1.49]Java Performance Tuning: http://javaperformancetuning.com/
[6.1.50]JDK6PerfUpdate:   http://austinjug.org/presentations/JDK6PerfUpdate_Dec2009.pdf
[6.1.51]深入分析 Java I/O 的工作机制:http://www.ibm.com/developerworks/cn/java/j-lo-javaio/
[6.1.52]Java IO流分析、IO整理与IO优化:http://www.tuicool.com/articles/BVJ36j
[6.1.53]IO的阻塞与非阻塞、同步与异步以及Java网络IO交互方式:http://www.cnblogs.com/zhuYears/archive/2012/09/28/2690194.html

6.2 Linux(OS)相关:

[6.2.1]《Linux Performance and Tuning Guidelines.pdf》:http://www.redbooks.ibm.com/redpapers/pdfs/redp4285.pdf
[6.2.2]《 Linux 性能及调优指南》:http://hi.baidu.com/imlidapeng/item/87b0f8d2173ce18f6cce3f70
[6.2.3]《Linux服务器性能调整.pdf》
[6.2.4]  陈浩-性能调优攻略:http://coolshell.cn/articles/7490.html
[6.2.5]  Linux按照CPU、内存、磁盘IO、网络性能监测 http://my.oschina.net/chape/blog/159640#OSC_h1_1
[6.2.6]  《Nginx模块开发与架构解析》

6.3 实验环境软硬件相关

[6.3.1]  性能调优指南--计算机系统Java应用程序(不断完善)代码示例下载地址:http://blog.csdn.net/ajian005/article/details/18324407
[6.3.2] JVM:Sun JDK6.0
[6.3.3] OS: RedHat Linux AS4  
[6.3.4] Linux Kernel : Linux localhost 2.6.9-89.ELsmp #1 SMP Mon Apr 20 10:34:33 EDT 2009 i686 i686 i386 GNU/Linux
[6.3.5] CPU T7250 双核,Memory:3G, 硬盘5400转250G,网卡100M

转载请保持原链接地址:http://download.csdn.net/detail/ajian005/6907817

抱歉!评论已关闭.