有人懂jvm调优面试题吗

5099人阅读
深入理解JVM性能调优
&&& 在上文中我们分析了很多性能监控工具,介绍这些工具的目的只有一个,那就是找出对应的性能瓶颈。盲目的性能调优是没有效果的,只有充分知道了哪里出了问题,针对性的结果才是立竿见影的。解决了主要的性能问题,那些次要的性能问题也就不足为虑了!我们知道,性能问题无非就这么几种:CPU、内存、磁盘IO、网络。那我们来逐一介绍以下相关的现象和一些可能出现的问题。一、CPU过高。查看CPU最简单的我们使用任务管理器查看,如下图所示,windows下使用任务管理器查看,Linux下使用top查看。一般我们的服务器都采用Linux,因此我们重点关注一下Linux(注:windows模式下相信大家已经很熟悉了,并且前面我们已经提到,使用资源监视器可以很清楚的看到系统的各项参数,在这里我就不多做介绍了)在top视图下,对于多核的CPU,显示的CPU资源有可能超过100%,因为这里显示的是所有CPU占用百分百的总和,如果你需要看单个CPU的占用情况,直接按键1就可以看到。如下图所示,我的一台测试机为8核16GB内存。&在top&视图下,按键&shift+h&后,会显示各个线程的&CPU&资源消耗情况,如下图所示:&我们也可以通过sysstat&工具集的&pidstat&来查看注:sysstat下载地址:安装方法:1、chmod +x configure2、./configure3、make4、make install如输入pidstat 1 2就会隔一秒在控制台输出一次当然CPU的情况,共输出2次&除了top&、&pidstat&以外,&vmstat&也可以进行采样分析&相关top&、&pidstat&、&mstat&的用法大家可以去网上查找。下面我们主要来介绍以下当出现CPU过高的时候,或者CPU不正常的时候,我们该如何去处理?CPU消耗过高主要分为用户进程占用CPU过高和内核进程占用CPU过高(在Linux下top视图下us指的是用户进程,而sy是指内核进程),我们来看一个案例:&程序运行前,系统运行平稳,其中蓝色的线表示总的CPU&利用率,而红色的线条表示内核使用率。部署&war&测试程序,运行如下图所示:&对于一个web&程序,还没有任何请求就占用这么多&CPU&资源,显然是不正常的。并且我们看到,不是系统内核占用的大量&CPU&,而是系统进程,那是哪一个进程的呢?我们来看一下。&很明显是我们的java&进程,那是那个地方导致的呢?这就需要用到我们之前提到的性能监控工具。在此我们使用可视化监控工具&VisualVM。&首先我们排除了是GC&过于频繁而导致大&CPU&过高,因为很明显监控视图上没有&GC&的活动。然后我们打开&profilter&去查看以下,是那个线程导致了&CPU&的过高?&前面一些线程都是容器使用的,而下面一个线程也一直在执行,那是什么地方调用的呢?查找代码中使用ThredPoolExecutor&的地方。终于发现以下代码。&&&&private&BlockingQueue&queue;&&&&private&Executor&executor;//……public&void&run() {&&&&&&&&while(true){&&&&&&&&&&&try&{&&&&&&&&&&&&&&SendMsg sendMsg =&queue.poll();//从队列中取出&&&&&&&&&&&&&&if(null&!= sendMsg) {&&&&&&&&&&&&&&&&&&sendForQueue(sendMsg);&&&&&&&&&&&&&&}&&&&&&&&&&&}&catch&(Exception e) {&&&&&&&&&&&&&&e.printStackTrace();&&&&&&&&&&&}&&&&&&&}&&&&}问题很显然了,我们看一下对应BlockingQueue的poll方法的API文档。&不难理解了,虽然使用了阻塞的队列,但是使用了非阻塞的取法,当数据为空时直接返回null&,那这个语句就等价于下面的语句。@Override&&&&public&void&run() {&&&&&&&while(true){&&&&&&&&&&&&&&&&&}&&&&}相当于死循环么,很显然是非常耗费CPU资源的,并且我们还可以发现这样的死循环是耗费的单颗CPU资源,因此可以解释上图为啥有一颗CPU占用特别高。我们来看一下部署在Linux下的top视图。&猛一看,不是很高么?我们按键1&来看每个单独&CPU&的情况!&这下看的很清楚了吧!明显一颗CPU&被跑满了。(因为一个单独的死循环只能用到一颗&CPU&,都是单线程运行的)。问题找到,马上修复代码为阻塞时存取,如下所示:@Override&&&&public&void&run() {&&&&&&&while(true){&&&&&&&&&&&try&{&&&&&&&&&&&&&&SendMsg sendMsg =&queue.take();//从队列中取出&&&&&&&&&&&&&&sendForQueue(sendMsg);&&&&&&&&&&&}&catch&(Exception e) {&&&&&&&&&&&&&&e.printStackTrace();&&&&&&&&&&&}&&&&&&&}&&&&}&再来监控CPU&的变换,我们可以看到,基本上不消耗&CPU&资源(是我没做任何的访问哦,有用户建立线程就会消耗)。&再来看java&进程的消耗,基本上不消耗&CPU&资源&再来看VisualVM的监控,我们就可以看到基本上都是容器的一些线程了&以上示例展示了CPU&消耗过高情况下用户线程占用特别高的情况。也就是&Linux&下&top&视图中&us&比较高的情况。发生这种情况的原因主要有以下几种:程序不停的在执行无阻塞的循环、正则或者纯粹的数学运算、&GC&特别频繁。CPU过高还有一种情况是内核占用CPU很高。我们来看另外一个示例。package&com.yhj.jvm.monitor.cpu.&import&java.util.Rimport&java.util.concurrent.ExecutorSimport&java.util.concurrent.E&/**&*&@Described:系统内核占用CPU过高测试用例&*&@author&YHJ create at 2012-3-28&下午05:27:33&*&@FileNmae&com.yhj.jvm.monitor.cpu.sy.SY_Hign_TestCase.java&*/public&class&SY_Hign_TestCase {&&&&&&&private&final&static&int&LOCK_COUNT&= 1000;&&&&&//默认初始化LOCK_COUNT个锁对象&&&&private&Object []&locks&=&new&Object[LOCK_COUNT];&&&&&private&Random&random&=&new&Random();&&&&&//构造时初始化对应的锁对象&&&&public&SY_Hign_TestCase() {&&&&&&&for(int&i=0;i&LOCK_COUNT;++i)&&&&&&&&&&&locks[i]=new&Object();&&&&}&&&&&&&abstract&class&Task&implements&Runnable{&&&&&&&&protected&Object&lock;&&&&&&&&public&Task(int&index) {&&&&&&&&&&&this.lock=&locks[index];&&&&&&&}&&&&&&&@Override&&&&&&&public&void&run() {&&&&&&&&&&&while(true){&&//循环执行自己要做的事情&&&&&&&&&&&&&&doSth();&&&&&&&&&&&}&&&&&&&}&&&&&&&//做类自己要做的事情&&&&&&&public&abstract&void&doSth();&&&&}&&&&&//任务A&休眠自己的锁&&&&class&TaskA&extends&Task{&&&&&&&&public&TaskA(int&index) {&&&&&&&&&&&super(index);&&&&&&&}&&&&&&&&@Override&&&&&&&public&void&doSth() {&&&&&&&&&&&synchronized&(lock) {&&&&&&&&&&&&&&try&{&&&&&&&&&&&&&&&&&&lock.wait(random.nextInt(10));&&&&&&&&&&&&&&}&catch&(InterruptedException e) {&&&&&&&&&&&&&&&&&&e.printStackTrace();&&&&&&&&&&&&&&}&&&&&&&&&&&}&&&&&&&}&&&&&}&&&&&//任务B&唤醒所有锁&&&&class&TaskB&extends&Task{&&&&&&&&&&&&&public&TaskB(int&index) {&&&&&&&&&&&super(index);&&&&&&&&}&&&&&&&&@Override&&&&&&&public&void&doSth() {&&&&&&&&&&&try&{&&&&&&&&&&&&&&synchronized&(lock) {&&&&&&&&&&&&&&&&&&lock.notifyAll();&&&&&&&&&&&&&&&&&&Thread.sleep(random.nextInt(10));&&&&&&&&&&&&&&}&&&&&&&&&&&}&catch&(InterruptedException e) {&&&&&&&&&&&&&&e.printStackTrace();&&&&&&&&&&&}&&&&&&&}&&&&&}&&&&//启动函数&&&&public&void&start(){&&&&&&&ExecutorService service = Executors.newCachedThreadPool();&&&&&&&for(int&i=0;i&LOCK_COUNT;++i){&&&&&&&&&&&service.execute(new&TaskA(i));&&&&&&&&&&&service.execute(new&TaskB(i));&&&&&&&}&&&&}&&&&//主函数入口&&&&public&static&void&main(String[] args) {&&&&&&&new&SY_Hign_TestCase().start();&&&&}&}代码很简单,就是创建了2000个线程,让一定的线程去等待,另外一个线程去释放这些资源,这样就会有大量的线程切换,我们来看下效果。&很明显,CPU&的内核占用率很高,我们拿具体的资源监视器看一下:&很明显可以看出有很多线程切换占用了大量的CPU&资源。同样的程序部署在Linux下,top视图如下图所示:&展开对应的CPU&资源,我们可以清晰的看到如下情形:&大家可以看到有大量的sy&内核占用,但是也有不少的&us&,&us&是因为我们启用了大量的循环,而&sy&是因为大量线程切换导致的。我们也可以使用vmstat来查看,如下图所示:&二、文件IO&消耗过大,磁盘队列高。在windows环境下,我们可以使用资源监视器查看对应的IO消耗,如下图所示:&这里不但可以看到当前磁盘的负载信息,队列详情,还能看到每个单独的进程的资源消耗情况。Linux下主要使用pidstat、iostat等进行分析。如下图所示Pidstat –d –t –p [pid] {time} {count}如:pidstat -d -t -p Iostat&Iostat –x xvda 1 10做定时采样&废话不多说,直接来示例,上干货!package&com.yhj.jvm.monitor.&import&java.io.BufferedWimport&java.io.FileWimport&java.io.IOEimport&java.util.concurrent.ExecutorSimport&java.util.concurrent.E&/**&*&@Described:IO测试用例&*&@author&YHJ create at 2012-3-29&上午09:56:06&*&@FileNmae&com.yhj.jvm.monitor.io.IO_TestCase.java&*/public&class&IO_TestCase {&&&&&&&private&String&fileNmae&=&&monitor.log&;&&&&&&&private&String&context&;&&&&&&&//&和CPU处理器个数相同,既充分利用CPU资源,又导致线程频繁切换&&&&private&final&static&int&THRED_COUNT&= Runtime.getRuntime().availableProcessors();&&&&&&&public&IO_TestCase() {//加长写文件的内容,拉长每次写入的时间&&&&&&&StringBuilder sb =&new&StringBuilder();&&&&&&&for(int&i=0;i&1000;++i){&&&&&&&&&&&sb.append(&context index :&)&&&&&&&&&&&.append(i)&&&&&&&&&&&.append(&\n&);&&&&&&&&&&&this.context=&new&String(sb);&&&&&&&}&&&&}&&&&//写文件任务&&&&class&Task&implements&Runnable{&&&&&&&&@Override&&&&&&&public&void&run() {&&&&&&&&&&&while(true){&&&&&&&&&&&&&&BufferedWriter writer =&null;&&&&&&&&&&&&&&try&{&&&&&&&&&&&&&&&&&&writer =&new&BufferedWriter(new&FileWriter(fileNmae,true));//追加模式&&&&&&&&&&&&&&&&&&writer.write(context);&&&&&&&&&&&&&&}&catch&(Exception e) {&&&&&&&&&&&&&&&&&&e.printStackTrace();&&&&&&&&&&&&&&}finally{&&&&&&&&&&&&&&&&&&try&{&&&&&&&&&&&&&&&&&&&&&writer.close();&&&&&&&&&&&&&&&&&&}&catch&(IOException e) {&&&&&&&&&&&&&&&&&&&&&e.printStackTrace();&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&}&&&&&&&&&&&}&&&&&&&&&&&&&&&&&}&&&&}&&&&//启动函数&&&&public&void&start(){&&&&&&&ExecutorService service = Executors.newCachedThreadPool();&&&&&&&for(int&i=0;i&THRED_COUNT;++i)&&&&&&&&&&&service.execute(new&Task());&&&&}&&&&//主函数入口&&&&public&static&void&main(String[] args) {&&&&&&&new&IO_TestCase().start();&&&&}&}这段示例很简单,通过创建一个和CPU个数相同的线程池,然后开启这么多线程一起读写同一个文件,这样就会因IO资源的竞争而导致IO的队列很高,如下图所示:&关掉之后马上就下来了&我们把这个部署到Linux&上观看。&这里的%idle&指的是系统没有完成写入的数量占用&IO&总量的百分百,为什么这么高我们的系统还能承受?因为我这台机器的内存为16GB&的,我们来查看以下&top&视图就可以清晰的看到。&占用了大量的内存资源。三、内存消耗对于JVM的内存模型大家已经很清楚了,前面我们讲了JVM的性能监控工具。对于Java应用来说,出现问题主要消耗在于JVM的内存上,而JVM的内存,JDK已经给我们提供了很多的工具。在实际的生成环境,大部分应用会将-Xms和-Xmx设置为相同的,避免运行期间不断开辟内存。对于内存消耗,还有一部分是直接物理内存的,不在堆空间,前面我们也写过对应的示例。之前一个系统就是因为有大量的NIO操作,而NIO是使用物理内存的,并且开辟的物理内存是在触发FULL GC的时候才进行回收的,但是当时的机器总内存为16GB&给堆的内存是14GB Edon为1.5GB,也就是实际剩下给物理呢哦村的只有0.5GB,最终导致总是发生内存溢出,但监控堆、栈的内存消耗都不大。在这里我就不多写了!四、网络消耗过大Windows下使用本地网络视图可以监控当前的网络流量大小&更详细的资料可以打开资源监视器,如下图所示&Linux平台可以使用以下&sar&命令查看sar -n DEV 1 2&字段说明:rxpck/s:每秒钟接收的数据包txpck/s:每秒钟发送的数据包rxbyt/s:每秒钟接收的字节数txbyt/s:每秒钟发送的字节数rxcmp/s:每秒钟接收的压缩数据包txcmp/s:每秒钟发送的压缩数据包rxmcst/s:每秒钟接收的多播数据包Java程序一般不会出现网络IO导致问题,因此在这里也不过的的阐述。五、程序执行缓慢当CPU、内存、磁盘、网络都不高,程序还是执行缓慢的话,可能引发的原因大致有以下几种:1程序锁竞争过于激烈,比如你只有2颗CPU,但是你启用了200个线程,就会导致大量的线程等待和切换,而这不会导致CPU很高,但是很多线程等待意味着你的程序运行很慢。2未充分利用硬件资源。比如你的机器是16个核心的,但是你的程序是单线程运行的,即使你的程序优化的很好,当需要处理的资源比较多的时候,程序还会很慢,因此现在都在提倡分布式,通过大量廉价的PC机来提升程序的执行速度!3其他服务器反应缓慢,如数据库、缓存等。当大量做了分布式,程序CPU负载都很低,但是提交给数据库的sql无法很快执行,也会特别慢。总结一下,当出现性能问题的时候我们该怎么做?一、CPU过高1、us过高使用监控工具快读定位哪里有死循环,大计算,对于死循环通过阻塞式队列解决,对于大计算,建议分配单独的机器做后台计算,尽量不要影响用户交互,如果一定要的话(如框计算、云计算),只能通过大量分布式来实现2、sy过高最有效的方法就是减少进程,不是进程越多效率越高,一般来说线程数和CPU的核心数相同,这样既不会造成线程切换,又不会浪费CPU资源二、内存消耗过高1、及时释放不必要的对象2、使用对象缓存池缓冲3、采用合理的缓存失效算法(还记得我们之前提到的弱引用、幽灵引用么?)三、磁盘IO过高1、异步读写文件2、批量读写文件3、使用缓存技术4、采用合理的文件读写规则四、网络1、增加宽带流量五、资源消耗不多但程序运行缓慢1、使用并发包,减少锁竞争2、对于必须单线程执行的使用队列处理3、大量分布式处理六、未充分利用硬件资源1、修改程序代码,使用多线程处理2、修正外部资源瓶颈,做业务拆分3、使用缓存转自:http://yhjhappy234./blog/static//?suggestedreading&wumii
相关资讯& — 
相关文档& — 
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:712086次
积分:7855
积分:7855
排名:第2780名
原创:33篇
转载:503篇
评论:60条
(16)(15)(2)(10)(25)(26)(16)(23)(15)(3)(7)(8)(18)(17)(13)(10)(16)(5)(30)(42)(16)(12)(23)(12)(18)(4)(4)(18)(3)(5)(17)(8)(4)(5)(4)(16)(15)(6)(4)(1)(5)(1)(8)(12)(1)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'20530人阅读
性能优化(88)
架构设计(74)
压力测试(17)
JVM调优(4)
玩过性能优化的朋友都清楚,性能优化的关键并不在于怎么进行优化,而在于怎么找到当前系统的性能瓶颈。性能优化分为好几个层次,比如系统层次、算法层次、代码层次...JVM 的性能优化被认为是底层优化,门槛较高,精通这种技能的人比较少。笔者呆过几家技术力量不算弱的公司,每个公司内部真正能够进行 JVM 性能调优的人寥寥无几、甚至没有。如是乎,能够有效通过 JVM 调优提升系统性能的人往往被人们冠以&大牛&、&大师&之类的称呼。其实 JVM 本身给我们提供了很多强大而有效的监控进程、分析定位瓶颈的工具,比如 JConsole、JMap、JStack、JStat 等等。使用这些命令,再结合 Linux 自身提供的一些强大的进程、线程命令,能够快速定位系统瓶颈。本文以一次使用这些工具准确定位到某系统瓶颈的实战经验为例,希望能为大家掌握 JVM 调优这项技能起到一些借鉴作用。本文背景:Linux:RedHat 6.1Weblogic:11gJRokit:R28.2.4JDK:1.6.0_45Weblogic 跑在 JRokit 上面,JDK 是我们对 JRokit 进行监控分析的工具。1. LoadRunner 压测结果该系统其实早已跑在生产上好多年,虽然没有做过压力测试,但稳定性还是可以的。公司进行架构调整,有一些新系统将对接该系统。公司领导担心对接后该系统的并发性能问题,于是开始对该系统进行并发压力测试。50 个用户并发压十几个小时,TRT 在 20 左右,TPS 在 2.5 左右。领导对这个结果不满意,要求进行优化。但这是个老系统,开发在之前明显已经对其代码做过很多优化,如今面对这种状况也束手无策。2. Oracle 的 awr 报告分析有句老话,优化来优化去,系统的性能瓶颈还是会在数据库上面。在这里我们首先想到的也是数据库的问题。并发压测的时候
查看数据库服务器各项性能指标,很清闲。分析 awr 报告,结果显示也是很清闲。并发压测的时候去相关数据表执行一些 sql 的速度很快也证明着问题不在 Oracle 这边。3. Weblogic 服务器的性能指标使用
查看并发压测时的 Weblogic 所在的 Linux 服务器,除了 CPU 之外其它各项指标显示,Linux 也很清闲。虽然 CPU 利用率始终在 200% 左右徘徊,但这对于 16 核的系统来讲也算是正常的吧?4. JStack 报告分析事情到了这里,大家已经想到了线程死锁、等待的问题了。没错,JStack 隆重登场。JStack 能够看到当前 Java 进程中每个线程的当前状态、调用栈、锁住或等待去锁定的资源,而且很强悍的是它还能直接报告是否有线程死锁,可谓解决线程问题的不二之选。$ /opt/jdk1.6.0_45/bin/jstack -l 10495 & ~/10495jstack.txtJRokit 堆栈的拉取,可以直接用 JDK 的 JStack,10495 是 Weblogic 服务的进程 ID。注意一定要用该进程的启动用户去拉,否则会报 Unable to open socket file: target process not responding or HotSpot VM not loaded 错误。JStack 拉取的文件信息基本分为以下几个部分:该拉取快照的服务器时间JVM 版本以线程 ID(即 tid)升序依次列出当前进程中每个线程的调用栈死锁(如果有的话)阻塞锁链打开的锁链监视器解锁情况跟踪每个线程在等待什么资源,这个资源目前在被哪个线程 hold,尽在眼前。JStack 最好在压测时多次获取,找到的普遍存在的现象即为线程瓶颈所在。4.1. TLA 空间的调整多次拉取 JStack,发现很多线程处于这个状态:& & at jrockit/vm/Allocator.getNewTla(JJ)V(Native Method)& & at jrockit/vm/Allocator.allocObjectOrArray(Allocator.java:354)[optimized]& & at java/util/HashMap.resize(HashMap.java:564)[inlined]& & at java/util/LinkedHashMap.addEntry(LinkedHashMap.java:414)[optimized]& & at java/util/HashMap.put(HashMap.java:477)[optimized]由此怀疑出现上述堆栈的原因可能是 TLA 空间不足引起。TLA 是 thread local area 的缩写,是每个线程私有的空间,所以在多线程环境下 TLA 带来的性能提升是显而易见的。如果大部分线程的需要分配的对象都较大,可以考虑提高 TLA 空间,因为这样更大的对象可以在 TLA 中进行分配,这样就不用担心和其它线程的同步问题了。但这个也不可以调的太大,否则也会带来一些问题,比如会带来更多内存碎片、更加频繁的垃圾搜集。TLA 默认最小大小 2 KB,默认首选大小 16 KB - 256 KB (取决于新生代分区大小)。这里我们调整 TLA 空间大小为最小 32 KB,首选 1024 KB,JVM 启动参数中加入:-XXtlaSize:min=32k,preferred=1024k5. JStat 结合 GC 日志报告分析第 4 步参数生效之后继续压测,TLA 频繁申请是降下来了,但 TRT 仍旧是 20,TPS 依旧 2.5。别灰心,改一个地方就立竿见影,胜利似乎来得太快了点。现在怀疑是服务堆内存太小,查看一下果然。服务器物理内存 32 GB,Weblogic 进程只分到了 6 GB。怎么查看?至少有四种办法:5.1. ps 命令$ ps -ef | grep javadefonds & &
&2 Sep03 ? & & & &09:03:17 /opt/jrockit-jdk1.6.0_33/bin/java -jrockit -Xms6000m -Xmx6000m -Dweblogic.Name=AdminServer -Djava.security.policy=5.2. Weblogic 控制台登录 Weblogic 管理控制台 -& 环境 -& 服务器,选择该服务器实例 -& 监视 -& 性能 -& 当前堆大小。这个页面还能看到进程已运行时间,启动以来发生的 GC 次数,可以折算出 GC 的频率,为本次性能瓶颈 - GC 过于频繁提供有力的佐证。5.3. GC 日志报告开启 JRokit GC 日志报告只需在 Java 进程启动参数里加入-Xverbose:memory -Xverboselog:verboseText.txtGC 日志将会被输出到 verboseText.txt 文件,这个文件一般会生成在启动的 Weblogic 域目录下。如果找不着也可以用 find 命令去搜:$ find /appserver/ -name verboseText.txt/appserver/Oracle/Middleware/user_projects/domains/defonds_domain/verboseText.txtGC log 拿到后,第 3 行中的 maximal heap size 即为该进程分配到的最大堆大小:[INFO ][memory ] Heap size: KB, maximal heap size: KB, nursery size: 5242880KB.下面还有进程启动以后较为详细的每次 GC 的信息:[INFO ][memory ] [YC#8-340.845: YC KB-&KB (KB), 0.018 s, sum of pauses 17.294 ms, longest pause 17.294 ms.[INFO ][memory ] [YC#2-340.871: YC KB-&KB (KB), 0.019 s, sum of pauses 18.779 ms, longest pause 18.779 ms.[INFO ][memory ] [YC#8-340.895: YC KB-&KB (KB), 0.017 s, sum of pauses 16.520 ms, longest pause 16.520 ms.[INFO ][memory ] [OC#614] 340.895-341.126: OC KB-&KB (KB), 0.231 s, sum of pauses 206.458 ms, longest pause 206.458 ms.第一行表示该进程启动后的第 340.828 秒 - 340.845 秒期间进行了一次 young gc,该次 GC 持续了 17.294 ms,将整个已用掉的堆内存由
KB。第三行同样是一次 young gc,但该次 GC 后已用堆内存反而上升到了
KB,也就是达到最大堆内存,于是该次 young gc 结束的同时触发 full gc。第四行是一次 old gc (即 full gc),将已用堆内存由
KB,耗时 206.458 ms。这些日志同样能够指出当前压力下的 GC 的频率,为本次性能瓶颈 - GC 过于频繁提供有力的佐证。5.4. JStat 报告跟 JStack 的拉取一样,可以直接用 JDK 的 JStat 去拉取 JRokit 的 GC 信息:$ /opt/jdk1.6.0_45/bin/jstat -J-Djstat.showUnsupported=true -snap 10495 & ~/10495jstat.txt注意这个信息是一个快照,这是跟 GC 日志报告不同的地方。jrockit.gc.latest.heapSize=jrockit.gc.latest.nurserySize=上述是当前已用碓大小和新生代分区大小。多拉几次即可估算出各自分配的大小。5.5. 内存分配根据 5.1 - 5.4 我们得出当前服务器分配堆内存太小的结论,根据 5.3 GC 日志报告和 5.4. JStat 报告可以得出新生代分区太小的结论。于是我们调整它们的大小,结合 4.1 TLA 调整的结论,JVM 启动参数增加以下:-Xms10240m -Xmx10240m -Xns:1024m -XXtlaSize:min=32k,preferred=1024k再次压测,TRT 降到了 2.5,TPS 上升到 20。6. 性能瓶颈的定位很明显,上述 JVM 调整没有从根本上解决性能问题,我们还没有真正定位到系统性能瓶颈。6.1. 性能线程的定位6.1.1. 性能进程的获取使用 TOP 命令拿到最耗 CPU 的那个进程:进程 ID 为 10495 的那个进程一直在占用很高的 CPU。6.1.2. 性能线程的获取现在我们来找到这个进程中占用 CPU 较高的那些线程:$ ps p 10495 -L -o pcpu,pid,tid,time,tname,cmd & ~/10495ps.txt多次拉这个快照,我们找到了 tid 为 10499、10500、10501、10502 等线程一直在占用着很高的 CPU:拉 JStack 快照看看都是一些什么线程:$ /opt/jdk1.6.0_45/bin/jstack -l 10495 & ~/10495jstack.txt相关部分结果如下:&(GC Worker Thread 1)& id=? idx=0x10 tid=10499 prio=5 alive, daemon& & at pthread_cond_wait@@GLIBC_2.3.2+202(:0)@0xa& & at eventTimedWaitNoTransitionImpl+71(event.c:90)@0x7fac47be8528& & at eventTimedWaitNoTransition+66(event.c:72)@0x7fac47be8593& & at mmGCWorkerThread+137(gcthreads.c:809)@0x7fac47c0774a& & at thread_stub+170(lifecycle.c:808)@0x7fac47cc15bb& & at start_thread+208(:0)@0x& & Locked ownable synchronizers:& & & & - None&(GC Worker Thread 2)& id=? idx=0x14 tid=10500 prio=5 alive, daemon& & at pthread_cond_wait@@GLIBC_2.3.2+202(:0)@0xa& & at eventTimedWaitNoTransitionImpl+71(event.c:90)@0x7fac47be8528& & at eventTimedWaitNoTransition+66(event.c:72)@0x7fac47be8593& & at mmGCWorkerThread+137(gcthreads.c:809)@0x7fac47c0774a& & at thread_stub+170(lifecycle.c:808)@0x7fac47cc15bb& & at start_thread+208(:0)@0x& & Locked ownable synchronizers:& & & & - None&(GC Worker Thread 3)& id=? idx=0x18 tid=10501 prio=5 alive, daemon& & at pthread_cond_wait@@GLIBC_2.3.2+202(:0)@0xa& & at eventTimedWaitNoTransitionImpl+71(event.c:90)@0x7fac47be8528& & at eventTimedWaitNoTransition+66(event.c:72)@0x7fac47be8593& & at mmGCWorkerThread+137(gcthreads.c:809)@0x7fac47c0774a& & at thread_stub+170(lifecycle.c:808)@0x7fac47cc15bb& & at start_thread+208(:0)@0x& & Locked ownable synchronizers:& & & & - None&(GC Worker Thread 4)& id=? idx=0x1c tid=10502 prio=5 alive, daemon& & at pthread_cond_wait@@GLIBC_2.3.2+202(:0)@0xa& & at eventTimedWaitNoTransitionImpl+71(event.c:90)@0x7fac47be8528& & at eventTimedWaitNoTransition+66(event.c:72)@0x7fac47be8593& & at mmGCWorkerThread+137(gcthreads.c:809)@0x7fac47c0774a& & at thread_stub+170(lifecycle.c:808)@0x7fac47cc15bb& & at start_thread+208(:0)@0x& & Locked ownable synchronizers:& & & & - None6.2. 找到性能瓶颈事情到了这里,已经不难得出当前系统瓶颈就是频繁 GC。为何会如此频繁 GC 呢?继续看 JStack,发现这两个互相矛盾的现象:一方面 GC Worker 线程在拼命 GC,但是 GC 前后效果不明显,已用堆内存始终降不下来;另一方面大量 ExecuteThread 业务处理线程处于 alloc_enqueue_allocation_and_wait_for_gc 的 native_blocked 阻塞状态。此外,停止压测以后,查看已用堆内存大小,也就几百兆,不到分配堆内存的 1/10。这说明了什么呢?这说明了我们应用里没有内存泄漏、静态对象不是太多、有大量的业务线程在频繁创建一些生命周期很长的临时对象。很明显还是代码里有问题。那么这些对象来自哪里?如何在海量业务代码里边准确定位这些性能代码?也就是说如何利用 JVM 调优驱动代码层面的调优?请参考博客《》,使用 TProfiler 我们成功找到了代码里边导致 JVM 频繁 GC 的元凶,并最终解决掉了这个性能瓶颈,将 TRT 降到了 0.5,TPS 提升至 100 +。参考资料
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:4549342次
积分:36051
积分:36051
排名:第124名
原创:240篇
转载:35篇
译文:160篇
评论:1495条
(1)(4)(6)(4)(5)(5)(5)(6)(5)(1)(3)(2)(2)(3)(2)(2)(1)(1)(1)(1)(3)(7)(4)(3)(5)(10)(10)(2)(2)(5)(3)(4)(4)(7)(4)(2)(1)(1)(6)(6)(4)(3)(4)(2)(21)(10)(3)(3)(5)(4)(1)(1)(3)(5)(3)(1)(1)(1)(1)(1)(3)(5)(15)(10)(1)(1)(2)(1)(2)(4)(3)(2)(1)(2)(1)(1)(1)(1)(3)(1)(1)(1)(1)(5)(3)(3)(1)(4)(1)(3)(11)(7)(7)(13)(7)(8)(1)(4)(6)(14)(16)(25)(1)(1)(1)(1)(1)(1)(1)

我要回帖

更多关于 tomcat性能调优 的文章

 

随机推荐