介    绍

在本文中,我们将讨论一些有助于提高Java应用程序性能的方法。我们将从如何定义可衡量的性能目标开始,然后查看不同的工具来衡量和监控应用程序性能,并找出瓶颈。

此外,还将介绍一些常见的Java代码级优化以及最佳编码实践。最后,我们将介绍JVM特定的调优技巧和体系结构更改,以提高Java应用程序的性能。

请注意,性能优化是一个广泛的主题,这只是在JVM上探索它的起点。

绩效目标

在我们开始致力于提高应用程序性能之前,我们需要定义和理解围绕关键领域的非功能性需求,例如可伸缩性,性能,可用性等。

以下是典型Web应用程序的一些常用性能目标:

1.    平均应用响应时间

2.    系统必须支持的平均并发用户数

3.    峰值负载期间 每秒的预期请求

使用可通过不同负载测试和应用程序监视工具,测量的这些指标,有助于识别关键瓶颈并相应地调整性能。

测试用例

让我们定义一下我们可以在本文中使用的基线应用程序。我们将使用一个简单的SpringBoot Web应用程序 - 就像我们在本文中创建的那样,此应用程序正在管理员工列表,并公开REST API以添加员工和检索现有员工。

我们将使用它作为运行负载测试的参考,并在接下来的部分,监控不同的应用程序指标。

    

识别瓶颈

负载测试工具和应用程序性能管理(APM)解决方案通常用于跟踪和优化Java应用程序的性能。围绕不同的应用场景运行负载测试,同时使用APM工具监控CPU,IO,堆使用情况等是识别瓶颈的关键。

Gatling是负载测试的最佳工具之一,它提供了对HTTP协议的出色支持- 这使其成为负载测试任何HTTP服务器的绝佳选择。

Stackify的Retrace是一个成熟的APM解决方案,具有丰富的功能 - 当然,这是帮助您确定此应用程序基线的好方法。Retrace的一个关键组件是其代码分析,它可以在不降低应用程序速度的情况下收集运行时信息。

Retrace还提供了用于监视正在运行的基于JVM的应用程序的内存,线程和类的小部件。除应用程序指标外,它还支持监视并托管我们应用程序的服务器的CPU和IO使用情况。

因此,像Retrace这样的成熟监控工具涵盖了解锁应用程序性能潜力的第一部分,第二部分实际上能够重现系统中的实际使用和负载。

这实际上比它看起来更难实现,并且理解应用程序的当前性能配置文件也很关键。这就是我们接下来要关注的内容。

负载试验

Gatling仿真脚本是用Scala编写的,但该工具还带有一个有用的GUI,能使我们记录场景。然后,GUI创建表示模拟的Scala脚本。

并且,在运行模拟之后,我们Gatling会生成有用的,可随时分析的HTML报告。

定义方案

在启动记录之前,我们需要定义一个场景。它将表示用户导航Web应用程序时会发生什么。

在我们的例子中,场景将是“让我们启动200个用户,每个用户发出10,000个请求”。

配置记录器

基于Gatling的第一步,使用以下代码创建一个新文件EmployeeSimulation  scala文件:

    val scn = scenario("FetchEmployees").repeat(10000) {
        exec(
          http("GetEmployees-API")
            .get("http://localhost:8080/employees")
            .check(status.is(200))
        )
    }
    setUp(scn.users(200).ramp(100))
}

运行负载测试

要执行负载测试,请运行以下命令:

$GATLING_HOME/bin/gatling.sh -s basic.EmployeeSimulation

对应用程序的API进行负载测试有助于查找细微的,难以发现的错误,例如数据库连接耗尽,请求在高负载期间超时,由于内存泄漏导致的不必要的高堆使用率等。

监控应用程序

要开始使用Retrace forJava应用程序,第一步是在Stackify上注册免费试用版。

一旦我们启动了要监视的Retrace代理和Java应用程序,我们就可以转到Retrace仪表板并单击AddApp链接。完成后,Retrace将开始监控我们的应用程序。

找到堆栈中最慢的部分

Retrace自动检测我们的应用程序并跟踪数十种常见框架和依赖项的使用,包括SQL,MongoDB,Redis,Elasticsearch等.Retrace可以轻松快速识别我们的应用程序出现性能问题的原因,如:

  • 某个SQL语句会让我们放慢脚步吗?

  • Redis突然变慢了吗?

  • 特定的HTTP Web服务下降还是慢

例如,下图提供了在给定持续时间内堆栈最慢部分周围的见解

代码级优化

负载测试和应用程序监控非常有助于识别应用程序中的一些关键瓶颈。但与此同时,我们需要遵循良好的编码实践,以便在我们开始应用程序监控之前避免许多性能问题。

让我们看一下下一节中的一些最佳实践。

使用StringBuilder进行字符串连接

字符串连接是一种非常常见的操作,也是一种低效的操作。简单地说,使用+=附加字符串的问题在于它将导致为每个新操作分配一个新的String

例如,这是一个简化但典型的循环- 首先使用原始连接,然后使用适当的构建器:

public String stringAppendLoop() {
    String s = "";
    for (int i = 0; i < 10000; i++) {
        if (s.length() > 0)
            s += ", ";
        s += "bar";
    }
    return s;
}
public String stringAppendBuilderLoop() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < 10000; i++) {
        if (sb.length() > 0)
            sb.append(", ");
        sb.append("bar");
    }
    return sb.toString();
}

在上面的代码中使用StringBuilder的效率要高得多,特别是考虑到这些基于String的操作的常见程度。

在我们继续之前,请注意当前一代JVM确实对字符串操作执行编译和/或运行时优化。

避免递归

导致StackOverFlowError递归代码逻辑是Java应用程序中的另一种常见方案。

如果我们不能废除递归逻辑,那么尾递归作为替代方案会更好。

让我们看看一个头部递归的例子:

public int factorial(int n) {
if (n == 0) {
return 1;
    } else {
return n * factorial(n - 1);
    }
}

现在让我们将它重写为tail recursive:

private int factorial(int n, int accum) {

if (n == 0) {
return accum;
    } else {
return factorial(n - 1, accum * n);
    }
}
public int factorial(int n) {
return factorial(n, 1);
}

其他JVM语言,例如Scala,已经具有编译器级支持来优化尾递归代码,并且还讨论了将这种类型的优化引入Java的问题。

小心使用正则表达式

正则表达式在很多场景中都很有用,但它们往往具有非常高的性能成本。了解各种JDKString方法也很重要,这些方法使用正则表达式,例如String.replaceAll()String.split()

如果绝对必须在计算密集型代码段中使用正则表达式,则值得缓存Pattern引用而不是重复编译:

 static final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");

使用像ApacheCommons Lang这样的流行库也是一个不错的选择,特别是对于操作字符串。

避免创建和销毁太多线程

创建和处理线程是JVM上性能问题的常见原因,因为线程对象的创建和销毁相对较重。

如果您的应用程序使用大量线程,则使用线程池很有意义,允许重用这些昂贵的对象。

为此,Java ExecutorService是此处的基础,并提供了一个高级API来定义线程池的语义并与之交互。

来自Java 7的Fork /Join框架也值得一提,因为它提供了一些工具,可以通过尝试使用所有可用的处理器内核来加速并行处理。为了提供有效的并行执行,框架使用一个名为ForkJoinPool的线程池来管理工作线程 

JVM调优

堆大小调整

确定生产系统的正确JVM堆大小不是一个简单的练习。第一步是通过回答以下问题来确定可预测的内存要求:

1.   我们计划将多少个不同的应用程序部署到单个JVM进程,例如,EAR文件,WAR文件,jar文件等的数量?

2.   在运行时可能加载多少个Java类; 包括第三方API?

3.   估计内存缓存所需的占用空间,例如,由我们的应用程序(和第三方API)加载的内部缓存数据结构,例如来自数据库的缓存数据,从文件读取的数据等。

4.   估计应用程序将创建的线程数。

如果没有一些真实的测试,这些数字很难估计。

了解应用程序需求的最可靠方法是对应用程序运行实际负载测试并在运行时跟踪度量标准。我们之前讨论的基于加特林的测试是一种很好的方法。

选择合适的垃圾收集器

垃圾收集周期,用于代表大多数面向客户的应用程序的响应性和整体Java性能的巨大问题。

它确实需要深入了解整个JVM上的GC,以及应用程序的特定配置文件 - 实现目标。

像分析器,堆转储和详细的GC日志记录等工具肯定会有所帮助。而且,这些都需要在实际负载模式中捕获,这是我们之前讨论过的Gatling性能测试的结果。

JDBC性能

关系数据库是典型Java应用程序中的另一个常见性能问题。为了获得完整请求的良好响应时间,我们必须自然地查看应用程序的每一层,并考虑代码如何与底层SQL DB交互。

连接池

让我们从众所周知的事实开始,即数据库连接很复杂,用一个连接池机制解决,这一方法是伟大的。

这里的推荐是HikariCP JDBC - 一个非常轻量级(大约130Kb)和快速的JDBC连接池框架。

JDBC批处理

我们处理持久性的方式的另一个方面是尝试尽可能批量操作。JDBC批处理允许我们在单个数据库往返中发送多个SQL语句。

在驱动程序和数据库端,性能提升都很重要。PreparedStatement是批处理的理想选择,一些数据库系统(例如Oracle)仅支持对预准备语句进行批处理。

另一方面,Hibernate更灵活,允许我们使用单一配置切换到批处理。

Statement缓存

接下来,Statement缓存是另一种可能提高持久层性能的方法 - 一种鲜为人知的性能优化,您可以轻松利用它。

根据底层JDBC驱动程序,您可以在客户端(驱动程序)或数据库端(语法树甚至执行计划)缓存PreparedStatement

向上扩展和向外扩展

数据库复制和分片也是提高吞吐量的绝佳方法,我们应该利用这些经过实战考验的体系架构模式来扩展企业应用程序的持久层。

结论

在本文中,我们探讨了许多有关提高Java应用程序性能的不同概念。我们从负载测试,基于APM工具的应用程序和服务器监控开始 - 然后是围绕编写高性能Java代码的一些最佳实践。

最后,我们研究了JVM特定的调优技巧,数据库端优化和体系架构更改,以扩展我们的应用程序。

长按二维码 ▲

订阅「架构师小秘圈」公众号

如有启发,帮我点个在看,谢谢↓

想知道Java程序如何才能提高性能吗?相关推荐

  1. Java 编程:如何提高性能?(简单总结篇)

    2019独角兽企业重金招聘Python工程师标准>>> 开发者在编程中除了要有编程规范,还要注意性能,在 Java 编程中有什么提高性能的好办法呢? 本文转自国内 ITOM 行业领军 ...

  2. 阿里面试回来,想和Java程序员谈一谈

    阿里面试回来,想和Java程序员谈一谈 引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后, ...

  3. 我从阿里面试回来,想和Java程序猿谈一谈

    最近小编看了一篇关于面试的文章,题目是"我从阿里面试回来,想和Java程序员谈一谈",内容不是讲面试前刷题,而是更加聚焦在面试前如何准备,以及工作当中如何学习.感觉总结的很不错,今 ...

  4. 百度三轮面试回来,想和Java程序员分享一下。

    转载自 百度三轮面试回来,想和Java程序员分享一下. 一,百度一面 1.给一个函数,返回 0 和 1,概率为 p 和 1-p,请你实现一个函数,使得返回 01 概率一样. 2.10 亿个 url,每 ...

  5. 阿里面试回来,想和 Java 程序员谈一谈

    转载自 阿里面试回来,想和 Java 程序员谈一谈! 第一个问题:阿里面试都问什么? 这个是让LZ最头疼的一个问题,也是群里的猿友们问的最多的一个问题.说实话,LZ只能隐约想起并发.JVM.分布式.T ...

  6. 头条面试归来,有些话想和Java程序员说!

    前段时间,字节跳动在阿里巴巴的大本营杭州悄悄的建立一个研发中心,最近在疯狂招人. 相信最近一段时间,杭州的很多的互联网公司的开发人员都接到过猎头的电话.据了解,字节跳动杭州研发中心主要负责字节跳动新增 ...

  7. 某阿里大神想和java程序员说的话

    引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内容 ...

  8. 【转载】阿里面试回来,想和Java程序员谈一谈

    原文链接:http://www.techug.com/post/alibaba-interview-java-programmer.html 其实本来真的没打算写这篇文章,主要是我得记忆力不是很好,不 ...

  9. 性能测试瓶颈分析与系统调优(9)java程序GC机制及性能稳定性调优分析

    8.1 jvm资源监控工具 8.1.1jconsole监控工具 jmap:此工具在jdk安装目录的bin文件夹里面 jmap [option]<pid> 例如:jmap -heap 603 ...

最新文章

  1. 奇淫异巧之 PHP 后门
  2. 直播预告:基于动态词表的对话生成研究 | PaperWeekly x 微软亚洲研究院
  3. .NET及.NET Core系统架构
  4. react学习(44)----只更新它需要更新的部分
  5. 面试官:了解二叉树吗,平衡二叉树,红黑树?
  6. SpringCloud学习--微服务架构
  7. 被App Store拒绝的N个原因
  8. 王立平--android中的anim(动画)
  9. 基于python的在线考试系统-基于Django的在线考试系统
  10. Javascript校验含中文的字符串长度
  11. 邮箱显示exchange账号服务器错误,删除监视邮箱Exchange服务器不正常状态
  12. 涂上你的脸! 如何创建自定义Photoshop笔刷
  13. 用STVP擦除芯片写保护
  14. 矩阵分析之 伪逆矩阵,左逆,右逆,广义逆
  15. Wordpress主题制作基础教程
  16. python 递归,迭代器,生成器,面向过程编程
  17. Blood Cousins (dsu on tree + 求第k级祖先)
  18. TFN F7 光时域反射仪 给您不一样体验
  19. 服务器上跑的很慢-已解决
  20. 视频教程-Informatica PowerCenter 10.2 权威指南中文版-ETL

热门文章

  1. linux 误删除mysql表能恢复吗_Linux下Oracle误删除数据文件恢复操作
  2. 2019 ACM - ICPC 上海网络赛 E. Counting Sequences II (指数型生成函数)
  3. G - Shuffle‘m Up POJ - 3087
  4. php创建菜单_php实现微信公众号创建自定义菜单功能的实例代码
  5. 除了计算机中的二进制还有哪些进位制,除了十进位制还有什么进位制
  6. enter对应的keycode_键盘对应数字-keycode值大全
  7. PC软件/web网站/小程序/手机APP产品如何增加个人收款接口
  8. 从“零”开始的存储历程到“零”外置的云存储
  9. Oracle11g_同义词
  10. Linux Bash小结1