当前位置: 首页 > news >正文

使用JProfiler进行性能调优

Author:Zhang He ,Shao Sheldon 

简介:

如今web project越发庞大,一个项目中包含上百个jar包是经常碰到的情况。这也就导致项目启动越发缓慢。

针对此情况,可以考虑使用JProfiler对程序启动过程进行监听,找到影响性能的hot spots,对其进行分析,优化,藉此达到减少启动时间的目的。

本文旨在通过一个案例,介绍一种使用JProfiler进行性能优化的方法。

优化方法:

  1. 找到hot spots

所谓hot spots是指潜在的导致性能降低的热点。单纯通过人工找到这些热点费时费力,并不十分可行。鉴于这些热点通常是一些函数的大量调用,故可以通过JProfiler监听web project的启动过程,找到大量占用cpu时间的函数调用,这些调用即为可能的热点。

1.1JProfiler设置

JProfiler,选择”New Server Integration”à选择”Generic application server”à选择”On this computer”à选择相应的jdk.

之后采用默认选项,直到此步骤(如图1):

图1

根据说明,选中配置信息,并复制到Tomcat中server设置项的Arguments中(如图2):

图2

之后对刚刚建立的session设置Filter(如图3),以过滤不需要跟踪的内容:

图3

启动web project后Console会提示运行JProfiler,此时运行之前配置过的session即可监听web project启动时的状态。注意,启动过程中请勾选上 ”Record CPU data on startup ” .

1.2分析JProfiler运行结果:

我们可以重点关注函数的调用,在CPU Views中我们可以看到函数调用树(如图4),找到cpu占用比高的部分进行分析,从中挑出潜在的hot spots。

图4

本文所涉web project中的hot spots包括对Annotation的处理、对TLD的处理等,我们选择Annotation处理部分作为例子,进行后面的介绍。

2.代码优化

2.1分析原因

首先需要知道的是,这里cost相对较高的原因。

通过函数命名可以得知,这一部分的开销主要发生在Tomcat容器启动时对Annotation的处理。启动阶段的Annotation处理,我们很容易会想到Servlet 3.0新特性——注解。所以重点就聚焦在这部分的内容上。

2.2分析源码

既然知道性能瓶颈在于Tomcat对于Annotation的处理,接下来即可针对这部分的内容进行优化。首先把Tomcat源码下载下来,导入eclipse中。

通过函数调用树可以看出,很大一部分开销都发生在了ClassParser类中。所以首先弄清ClassParser类中做了什么。

ClassParse类的构造函数如下:

public ClassParser(InputStream file) {

this.file = new DataInputStream(new BufferedInputStream(file, BUFSIZE));   

}

它接受一个InputStream,并用它构造出一个DataInputStream,通过向上查找代码调用,可以分析出该Stream是jar文件的某个ClassEntry,即jar包中的class文件。

该类的parse()方法如下:

public JavaClass parse() throws IOException, ClassFormatException {

readID();

readVersion();

readConstantPool();

readClassInfo();

readInterfaces();

readFields();

readMethods();

readAttributes();

return new JavaClass(class_name, superclass_name, access_flags, constant_pool, interface_names, runtimeVisibleAnnotations);

}

其中调用了一系列read方法,该方法是将Stream中的class文件读取并封装成一个JavaClass类,可见该方法会涉及到大量的IO操作,成为了性能的瓶颈。

    加快IO操作可以想到下面几种思路:

  1. 绕过读取的过程
  2. 减少读取的内容
  3. 加快读取的速度

经过几次尝试,我们在加快启动速度方面,找到了突破口:

在对class文件的解析过程中,DataInputStream的一些read方法被反复调用,包括readInt(),readUnsignedShort () 等,以readInt() 为例:

public final int readInt() throws IOException {

        int ch1 = in.read();

        int ch2 = in.read();

        int ch3 = in.read();

        int ch4 = in.read();

        if ((ch1 | ch2 | ch3 | ch4) < 0)

            throw new EOFException();

        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));

}

在对class文件的解析过程中,DataInputStream的一些read操作,该操作由BufferedInputStream完成。

public synchronized int read() throws IOException {

         if (pos >= count) {

             fill();

             if (pos >= count)

                   return -1;

         }

         return getBufIfOpen()[pos++] & 0xff;

}

BufferedInputStream实际上使用一个缓存数组buf[] 去提高文件读取效率,但是其read方法却因此需要判断多次是否越界。考虑readInt() 方法,通过四次read() 方法调用读取出一个Int,总共需要8次越界判断。但理论上完全可以直接从buf中取出4个字节的长度,仅通过两次判断即可得到所需要的内容。这在大量的文件读写中,对性能的影响不可忽视。针对该方案,进行优化。

2.3代码优化

设计一个FastDataInputStream类继承自BufferedInputStream,实现DataInput接口。该类直接需要实现DataInput接口的方法,即对Int, Long, UnsignedShort等内容的读取。其中,对读取“固定长度”内容的方法需要进行优化,跳过read()方法,直接从buf[]中取出需要的内容。仍以readInt()为例:

public final int readInt() throws IOException {

    if(pos + 3 >= count){

               fillNew();

               if(pos + 3 >= count) throw new EOFException();

    }

    int ch1 = this.buf[pos++] & 0xff;

    int ch2 = this.buf[pos++] & 0xff;

    int ch3 = this.buf[pos++] & 0xff;

    int ch4 = this.buf[pos++] & 0xff;

    return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);

}

同时由于我们在DataInput接口中增加了一个buffer,这样会导致需要考虑边界问题。例如数据充满16长度的buf[],buf中前15个字节已被读取,此时读取int类型的时候,需要考虑对残余字节的处理。由于残余数据量不会太大,所以为了简化逻辑,可以考虑在数组读取越界的时候,直接将残余内容复制到buf的开头,然后重新充满数组:

private void fillNew() throws IOException {

    int remain = 0;

    if(pos < count){

           remain = count - pos;

           System.arraycopy(buf, pos, buf, 0, remain);

    }

    pos = 0;

    int n = this.in.read(buf, remain, buf.length - remain);

    count = pos + n + remain;

}

修改Tomcat容器中的ClassParser类的构造方法,使后续程序中直接使用FastDataInputStream类:

public ClassParser(InputStream file) {

if(isFDIS){

           this.file = new FastDataInputStream(file, BUFSIZE);

    } else {

this.file = new DataInputStream(new BufferedInputStream(file, BUFSIZE));

}

}

注:isFDIS 为自己定义的开关标识。

性能对比:

下表对FastDataInputStream的性能进行了对比。针对classpath下所有文件进行parse的时候,性能提升约18%;若只针对若干jar包进行parse,性能提升约为15%。

若所运行项目包含更大量的jar文件,该优化将得到更可观的效率提升。

表1:性能对比

 

All “jar” in Classpath

10 “jar” files only

DataInputStream

593ms

93ms

FastDataInputStream

488ms

79ms

 

 

总结:

本文主要介绍了使用JProfiler对项目性能进行优化的一个方法:通过性能分析软件找到hot sports,针对hot sports分析源码,最后得出可行的优化方案。优化过程要注意以下几点:

  1. 要有针对性的选择需要跟踪的目标类包(package),尽量预判一些可疑的目标,减小跟踪范围。
  2. 分析热点时,可以根据项目特点进行分析。例如最近项目做过哪些改动,哪些改动可能导致对性能的影响。
  3. 设计优化方案时,尝试从不同角度着手,设计多个备用方案,共同工作。一方面可以提升优化结果,另一方面也保证在某方案被证明无效时有其它选择。

目前该Patch已被Apache接受,并将集成在下一个版本中。同时该优化方法,也可广泛适用于其他应用。

相关文章:

  • DBA的新领域:调试Oracle(进阶篇)
  • Comet框架Pushlets的集成
  • 如何定制一个基于REST Service的ODBC驱动程序
  • Maven依赖版本冲突报告
  • Maven中的扁平化POM
  • 你好,HBase
  • Maven Build Tracking
  • 分布式文件系统概述
  • 调试Oracle 之一 基础篇
  • 基于Apache Mesos 构建高可靠,高可用的Jenkins CI
  • Kepler性能分析之M2E调优
  • Ebay开源 Pulsar:实时大数据分析平台
  • JS组件化验证检测
  • 基于云技术的集成测试代码覆盖率收集的一站式解决方案
  • 使用github pages + issues + api建立个人博客
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • 77. Combinations
  • AngularJS指令开发(1)——参数详解
  • E-HPC支持多队列管理和自动伸缩
  • iOS小技巧之UIImagePickerController实现头像选择
  • js 实现textarea输入字数提示
  • PaddlePaddle-GitHub的正确打开姿势
  • ReactNative开发常用的三方模块
  • redis学习笔记(三):列表、集合、有序集合
  • springboot_database项目介绍
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 一个项目push到多个远程Git仓库
  • 自制字幕遮挡器
  • kubernetes资源对象--ingress
  • SAP CRM里Lead通过工作流自动创建Opportunity的原理讲解 ...
  • 湖北分布式智能数据采集方法有哪些?
  • 进程与线程(三)——进程/线程间通信
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • !!Dom4j 学习笔记
  • # Pytorch 中可以直接调用的Loss Functions总结:
  • #100天计划# 2013年9月29日
  • #pragma multi_compile #pragma shader_feature
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • #我与Java虚拟机的故事#连载12:一本书带我深入Java领域
  • (4)logging(日志模块)
  • (分布式缓存)Redis持久化
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (深入.Net平台的软件系统分层开发).第一章.上机练习.20170424
  • (一)基于IDEA的JAVA基础10
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • :O)修改linux硬件时间
  • [ solr入门 ] - 利用solrJ进行检索
  • [.net]官方水晶报表的使用以演示下载
  • [Android] Android ActivityManager
  • [AutoSar]状态管理(五)Dcm与BswM、EcuM的复位实现
  • [BZOJ5250][九省联考2018]秘密袭击(DP)
  • [c#基础]值类型和引用类型的Equals,==的区别