基于Java应用的自顶向下性能优化方法

2018-11-19 11:05
软件导刊 2018年11期
关键词:线程内存性能

唐 科

(电子科技大学成都学院 计算机系,四川 成都 611731)

0 引言

在各类Java应用系统中,为了给大量并发用户提供7×24小时持续不间断响应流畅的访问体验,系统设计必须精良。同时,在后续开发与维护过程中也必须考虑扩展性、可靠性,其核心就是满足系统的性能需求,给用户提供优良的体验。

系统性能由应用程序、系统环境、硬件配置等诸多因素决定,一种特定的配置无法满足所有特性各异的上层应用性能需求[1]。在这些因素中,确定的硬件和系统架构决定了底层的访问速度与吞吐量。良好的顶层软件设计(应用程序设计)却能充分利用硬件和系统架构,最大限度地发挥系统资源的利用效率,形成性能优良的应用系统。所以,系统性能的优化是由上述诸多因素相互作用决定的。

Java应用系统性能深受开发者及用户重视,它是应用系统的基础,一旦受损后果都是灾难性的。所以,确保应用系统性能始终处于优良状态,其重要性毋庸置疑。基于此,研究人员提出了很多技术方法来优化系统性能,但这些方法各自为政,不能形成系统优化措施。针对此问题,本文提出自顶向下的Java应用系统性能优化方法,通过形成体系化的调优策略达到优化系统性能目的。

1 相关研究

在异构系统编程环境应用中,通过对编程容易度和编程应用性能的综合比较研究,得出表1与表2所示的结论[14]。Java具备良好的共享内存自动分配机制,应用设备内存无限制、编程容易,但其性能较其它几种语言偏低,所以,Java性能的优化一直处于持续推进中。

表1 异构系统编程环境与内存空间模型

Java虚拟机(Java Virtual Machine,JVM)是其工作核心,也是性能调优的重点。根据系统体系结构采用指令级并行和多处理器并行,提供多线程扩展,将Java线程和虚拟处理器间的对应关系与虚拟和物理处理器及操作系统间的对应关系分开处理[4]。通过监视运行情况确定其中运行热点,再将热点代码段集中优化编译后直接运行。用直接的内存引用替代对象句柄,提高内存分配效率[4]。用操作系统中的线程实现虚拟机中的Java线程,降低线程间的干扰,提供快速线程同步机制[4]。此外还采用即时编译、动态优化[4]以及字节码优化等技术[6]。

表2 异构系统编程环境及其性能、编程容易度比较

除JVM外,针对操作系统层次的性能,数据采集与监控分析也是研究重点。其中,文献[16]、[17]、[18]、[19]提出了资源监控系统及其实现,在操作系统层进行资源消耗的数据采集,如CPU占用时间、CPU负载、内存分配与占用、文件传输负载、网络传输负载等。通过对这些数据进行分析找到性能的瓶颈所在,并据此进行优化操作。文献[20]则以Java支持的自动垃圾回收机制的运行环境为切入点,深入研究内存泄露问题,并明确指出内存泄漏的检测应是低侵入性、合理负载、不影响应用与JVM的正常运行。

Java应用系统不仅涉及操作系统、JVM等底层,还与Web应用、数据库应用等密切相关,所以它们的优化也是必须关注的。文献[2]以JVM性能调优为基础,实现了Java Servlet模式下的WebGIS服务器性能优化。文献[5]、[7]则提出了服务器端的优化与Web前端程序代码优化相结合的方法,达到表现层的性能调优目的。文献[8]、[9]则通过研究结构设计、内存优化、索引优化、SQL优化等方式,对数据库的应用性能进行了调优。

大部分性能优化工作集中在底层,随着计算机硬件体系和制造技术的进步,以及JDK的不断推陈出新,底层的性能优化也不断得到提高。但是,Java应用系统的良好性能并不仅仅依靠底层的优化,而是必须形成一个完整的体系。相关工作缺少系统优化思想,各自为政,无法从整个系统角度调优系统性能。所以,本文在此基础上提出了一种自顶向下的Java应用系统性能优化方法,根据工程项目应用开发特点,从系统顶层设计开始进行性能优化,覆盖了应用层、容器层、数据持久化层、JVM层、操作系统与网络层。该方法将贯穿于应用项目设计之初直至应用项目的生命周期终结为止,是一个综合的系统工程。

2 自顶向下的系统性能优化方法

自顶向下的Java应用系统性能优化方法对系统进行整体性能优化,充分考虑了各个层面的性能调优以及它们之间的相互影响,并根据设计目标与需求进行平衡,取得系统整体性能最大化调优结果。该方法整体结构如图1所示。

图1 自顶向下Java应用系统性能优化方法结构

2.1 应用层

系统性能优化工作是全局性的而非局部性的,过去的一些优化案例[3]表明,系统部署运行以后进行的性能分析与调优可能会导致应用程序修改,为避免此种情况出现,在应用系统设计之初就应当纳入性能优化的相应工作,以良好的系统设计来规避许多潜在的性能问题,这便是应用层的性能优化。

应用层的性能优化包括系统设计优化与Java编码实现优化两部分。系统设计优化又包含了软件结构设计优化和算法设计优化,良好的软件结构设计对系统的整体性能有着至关重要的作用,它的应用会避免许多可能出现的性能问题。科学合理地使用设计模式将有助于形成良好的软件结构。例如:对于频繁使用的那些重量级对象采用单例模式,可减少new操作的次数,节约创建对象的时间,降低系统内存的使用频率。通过代理模式实现延迟加载,提高系统性能,加快系统的反应速度。应用享元模式复用重量级对象,节省重复创建对象带来的开销,减少创建对象的数量,优化内存结构。

算法设计优化根据应用的实际需求,合理使用数据结构,科学平衡时间、空间开销使之总体最优。例如通过使用缓冲协调上层组件和下层组件的性能差,减少等待时间等。为防止密集型的I/O操作成为系统瓶颈,要考虑缓冲技术。在程序中使用数据库连接池和线程池,只对重量级对象使用对象池技术。良好的算法和数据结构的效率对应用系统的性能优化是有益的[10-11]。

Java编码实现优化需要程序员具备良好的个人编程习惯,正确应用JDK API库中的各类方法,编写出高效精炼的代码,让应用程序执行更少的CPU指令,通过更短的执行路径实现程序功能,确保系统的整体最优性能。例如使用最优方法提高算法实现效率,使用StringBuilder代替字符串连接运算符“+”,多使用栈,尽量避免应用递归。递归非常消耗资源,在计算密集型的代码中,要避免使用正则表达式。不要调用高开销的方法,优化自定义hasCode()方法和equals()方法。减少对共享资源的竞争(锁竞争)频率,缩短锁持有的时间等。

应用层性能优化不仅为算法产生更有效率的代码,而且会降低GC频率,减少GC压力,间接促进JVM的优化。

2.2 容器层

容器层优化涉及到开发和生产运行两大阶段。开发阶段需要采用一些应用层编码实现的优化技术,还应注意根据实际工程项目需求分别进行数据库连接优化、网络访问优化、缓存应用优化、检索优化、文件的配置与访问优化等。开发阶段的性能测试也不能忽视,例如访问的压力测试等。项目开发过程中选用适当的测试工具进行性能测试,LoadRunner用于压力测试,Jmeter用于性能测试。测试一些常用指标,如响应时间、吞吐率、资源利用率、最大并发用户数[12-13]等。而生产运行阶段,在硬件设备上需要采用与开发阶段不同的配置,同时选用适当的数据采集与监听工具,收集组合参数信息,根据这些信息分析判断后再进行配置调谐、监听收集信息的迭代过程,直至性能优化满意达标为止。

容器本身的性能要受到配置的影响,正确合理的配置是容器性能的保证。以甲骨文公司的GlassFish为例,它有开发模式和生产模式两种。开发模式会允许JSP自动加载,检查每个页面是否有变化,开发者不用重新部署应用程序就能看到运行结果,所以开发模式以损失性能的代价换来了灵活性,它只适用于项目工程的开发阶段。而在生产运行阶段,应使容器配置为生产模式,它会关闭自动加载功能,避免了系统调用检查文件的时间戳,不会影响多线程并发访问同一JSP文件时容器的处理能力,也不会影响其应用扩展性[3]。

在容器中的Web应用程序开发部署中,根据已有项目实现经验,应遵循以下规则:使用init方法缓存静态数据和资源引用,如果静态引用资源则采用JSP的include指令,如果包含资源动态生成的响应,则用JSP的include标签。剔除JSP页面模板中保留的空格,可减少通过网络传递的文件大小,改善网络传输性能。在JSP中应用jsp:useBean,一般大多数情况下使用className属性,只在绝对必要时才使用beanName[3]。

处于生产模式下的容器还需对其进行综合监控,通过组合参数寻找问题点。在GlassFish应用中,对具有1-2个CPU的开发计算机来说,设置线程池的最大数为5;但对于多核多CPU的生产计算机,设置的线程池最大数应为硬件线程数的2倍[3]。

容器层优化的目的是消除性能瓶颈,充分利用系统资源。随着用户负载的增加,使应用能够进行垂直扩展和水平扩展。

2.3 数据持久化层

应用系统进行设计开发时,需要持久化层的优化。对于那些应用Java框架的系统,如Spring、Hibernate、MyBatis等,它们的性能表现完全依赖持久化层性能。例如:通过应用优化的键生成器,减少生成主键的代价,使用JDBC批处理 inserts/updates减少来回传输,定期清理向数据库添加或修改数据时保留的会话,使用二级查询缓存等。

数据持久化层提供对象-关系映射功能,并在Java应用中管理关系数据。采用Java领域模型的应用程序,通过该映射与关系型数据库交互。在此过程中,持久层的性能优化涉及到缓存容量配置、线程池配置、数据库锁策略等几个方面。

Java持久化API应用的二级缓存容量会影响应用程序的性能,如果应用程序频繁地访问缓存,则会产生大量内存,导致JVM也会频繁地进行垃圾收集,反而降低了应用程序性能。因此,合理配置缓存是关键。这是一个迭代过程,通过应用系统运行过程的数据采集,分析、计算、判断缓存的命中率,据此进行调节和合理设置。一般而言,其容量至少设置成事务使用的同类对象之和。线程池的配置则取决于调用模式,通用原则是:线程池的最小容量等于硬件线程数或虚拟处理器的数目,线程池的最大容量则等于硬件线程数或虚拟处理器数目的2倍。数据库锁策略需要根据项目工程的实际需求进行选择并保证数据的完整性。如果应用系统存在大量的访问用户,频繁地访问并更新数据,那么采用悲观锁能获得较好性能,避免了大量的事务回滚以及并发访问。反之,如果数据不被并发事务频繁地修改,则适合采用乐观锁[3]。

数据持久化层的优化工作也必须兼顾开发与生产运行阶段,不同阶段采用相应的策略才能最大限度地获取系统的优良性能。

2.4 JVM层

JVM为了满足各种应用需要,为程序运行提供了大量配置选项,但是这些选项并非对所有的Java应用都是最优的,某些配置选项对某类应用是最优的,然而对另外一些应用却未必最优。所以,JVM的配置选项有很强的针对性。在实际优化操作过程中,需要准确获取应用系统运行时的相关变化情况,它们会对JVM的优化产生直接影响。

JVM的优化需要全盘折衷考虑,它面临着牵一发而动全身的情况。因此,对现代JVM进行调优是一门艺术[3],往往满足了系统的某个需求常常会牺牲系统的另一方面需求。例如减少了内存消耗,却影响了系统的吞吐量以及系统延迟;减少应用程序部署使用的JVM数量又牺牲了应用程序的可用性[3]。所以,对于不同的应用系统因为侧重点不同, JVM的优化也是完全不同的。

常规做法是针对具体的应用系统,根据其性能测试结果不断优化配置,反复进行迭代,直到这一过程取得令人满意的指标结果为止。

2.5 操作系统与网络层

对操作系统进行性能监控,收集各类相关数据并进行性能分析,根据分析结果采取对应的优化措施。

CPU使用率一般分为用户态使用率和系统态使用率[3]。当应用执行操作系统调用的时间占总的CPU应用时间的百分比较低时,或者操作系统的共享资源无竞争或低竞争、I/O设备之间的交互较少时,降低CPU系统态使用率,升高CPU用户态使用率,以提高应用系统性能。所以,为达到性能最佳,应尽可能降低CPU系统态使用率。

通过监控内存的相关属性实时获取该资源的消耗情况,以便采取应对措施。例如系统在进行页面交换或使用虚拟内存时,Java应用或JVM就会出现性能问题,发生磁盘与内存之间的置换会影响应用的响应和吞吐量[3,15,19]。

网络I/O的性能则会影响Java应用的性能与扩展。系统运行过程中的优化必须进行网络I/O监控,通过采集的数据计算出网络I/O的使用率并采取相应措施。例如减少网络读写的系统调用,减少处理请求和发送响应的线程数以改善性能。

3 结语

Java应用系统的性能优化是一项贯穿于项目设计之初直至项目生命周期终结的综合系统工程,需充分考虑各个层面的性能调优以及它们之间的相互影响,并根据设计目标与需求进行平衡。前三层的优化是应用系统开发阶段的重点,围绕高效算法的设计进行,同时兼顾系统的恰当配置。后两层优化则根据应用系统的实际需求与拥有的计算资源进行综合性能调优。在自顶向下的方法中,通过前三层的优化设计降低后两层的调优难度,而后两层的调优则需要避免对前三层的代码进行修改。在后续工作中,应深入开展多层性能优化的理论模型研究,通过创建模型,精确计算与评估应用系统的性能优化程度,以定量分析的形式完善应用系统性能优化。

猜你喜欢
线程内存性能
外部高速缓存与非易失内存结合的混合内存体系结构特性评测
提供将近80 Gbps的带宽性能 DisplayPort 2.0正式发布
“春夏秋冬”的内存
浅谈linux多线程协作
Al-Se双元置换的基于LGPS的thio-LISICON的制备与性能表征
强韧化PBT/PC共混物的制备与性能
RDX/POLY(BAMO-AMMO)基发射药的热分解与燃烧性能
基于内存的地理信息访问技术
基于上下文定界的Fork/Join并行性的并发程序可达性分析*
Linux线程实现技术研究