编写高性能 Java 代码的最佳实践

Apollo MPC横纵向耦合控制学习笔记 Apollo MPC横纵向控制原理学习笔记 阅读详情

点击上方“芋道源码”,选择“设为星标”

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 8:55 更新文章,每天掉亿点点头发...

源码精品专栏

 

来源:雁惊寒


摘要:本文首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践。最后研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整。

以下是译文。

介绍

在这篇文章中,我们将讨论几个有助于提升Java应用程序性能的方法。我们首先将介绍如何定义可度量的性能指标,然后看看有哪些工具可以用来度量和监控应用程序性能,以及确定性能瓶颈。

我们还将看到一些常见的Java代码优化方法以及最佳编码实践。最后,我们将看看用于提升Java应用程序性能的JVM调优技巧和架构调整。

请注意,性能优化是一个很宽泛的话题,而本文只是对JVM探索的一个起点。


性能指标

在开始优化应用程序的性能之前,我们需要理解诸如可扩展性、性能、可用性等方面的非功能需求。

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

  • 应用程序平均响应时间

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

  • 在负载高峰期间,预期的每秒请求数

这些指标可以通过使用多种监视工具监测到,它们对分析性能瓶颈和性能调优有着非常大的作用。


示例应用程序

我们将使用一个简单的Spring Boot Web应用程序作为示例,在这篇文章中有相关的介绍。这个应用程序可用于管理员工列表,并对外公开了添加和检索员工的REST API。

我们将使用这个程序作为参考来运行负载测试,并在接下来的章节中监控各种应用指标。


找出性能瓶颈

负载测试工具和应用程序性能管理(APM)解决方案常用于跟踪和优化Java应用程序的性能。要找出性能瓶颈,主要就是对各种应用场景进行负载测试,并同时使用APM工具对CPU、IO、堆的使用情况进行监控等等。

Gatling是进行负载测试最好的工具之一,它提供了对HTTP协议的支持,是HTTP服务器负载测试的绝佳选择。

Stackify的Retrace是一个成熟的APM解决方案。它的功能很丰富,对确定应用程序的性能基线很有帮助。Retrace的关键组件之一是它的代码分析功能,它能够在不减慢应用程序的情况下收集运行时信息。

Retrace还提供了监视基于JVM应用程序的内存、线程和类的小部件。除了应用程序本身的指标之外,它还支持监视托管应用程序的服务器的CPU和IO使用情况。

因此,像Retrace这样功能全面的监控工具是解锁应用程序性能潜力的第一步。而第二步则是在你的系统上重现真实使用场景和负载。

说起来容易,做起来难,而且了解应用程序当前的性能也非常重要。这就是我们接下来要关注的问题。


Gatling负载测试

Gatling的模拟测试脚本是用Scala编写的,但该工具还附带了一个非常有用的图形界面,可用于记录具体的场景,并生成Scala脚本。

在运行模拟脚本之后,Gatling会生成一份非常有用的、可用于分析的HTML报告。

定义场景

在启动记录器之前,我们需要定义一个场景,表示用户在浏览Web应用时发生的事情。

在我们的这个例子中,具体的场景将是“启动200个用户,每个用户发出一万个请求。”

配置记录器

根据“Gatling的第一步”所述,用下面的代码创建一个名为EmployeeSimulation的scala文件:

class EmployeeSimulation extends Simulation {
    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-sbasic.EmployeeSimulation

对应用程序的API进行负载测试有助于发现及其细微的并且难以发现的错误,如数据库连接耗尽、高负载情况下的请求超时、因为内存泄漏而导致堆的高使用率等等。

监控应用程序

要使用Retrace进行Java应用程序的开发,首先需要在Stackify上申请免费试用账号。然后,将我们自己的Spring Boot应用程序配置为Linux服务。我们还需要在托管应用程序的服务器上安装Retrace代理,按照下面这篇文章所述的操作即可。

https://support.stackify.com/hc/en-us/articles/205419575

申请账号:

https://stackify.com/retrace/

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对性能的提升非常有效。请注意,现代的JVM会在编译或者运行时对字符串操作进行优化。

避免递归

导致出现StackOverFlowError错误的递归代码逻辑是Java应用程序中另一种常见的问题。如果无法去掉递归逻辑,那么尾递归作为替代方案将会更好。

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

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

现在我们把它重写为尾递归:

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)已经在编译器级支持尾递归代码的优化,当然,对于这种优化目前也存在着一些争议。

谨慎使用正则表达式

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

如果你不得不在计算密集的代码段中使用正则表达式,那么需要缓存Pattern的引用而避免重复编译:

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

使用一些流行的库,比如Apache Commons Lang也是一个很好的选择,特别是在字符串的操作方面。

避免创建和销毁过多的线程

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

如果应用程序使用了大量的线程,那么使用线程池会更加有用,因为线程池允许这些昂贵的对象被重用。

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

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


JVM调优

堆大小的调优

为生产系统确定合适的JVM堆大小并不是一件简单的事情。要做的第一步是回答以下问题以预测内存需求:

  • 计划要把多少个不同的应用程序部署到单个JVM进程中,例如EAR文件、WAR文件、jar文件的数量是多少?

  • 在运行时可能会加载多少个Java类,包括第三方API的类?

  • 估计内存缓存所需的空间,例如,由应用程序(和第三方API)加载的内部缓存数据结构,比如从数据库缓存的数据、从文件中读取的数据等等。

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

如果没有经过真实场景的测试,这些数字很难估计。

要获得有关应用程序需求的最好最可靠的方法是对应用程序执行实际的负载测试,并在运行时跟踪性能指标。我们之前讨论的基于Gatling的测试就是一个很好的方法。

选择合适的垃圾收集器

Stop-the-world(STW)垃圾收集的周期是影响大多数面向客户端应用程序响应和整体Java性能的大问题。但是,目前的垃圾收集器大多解决了这个问题,并且通过适当的优化和大小的调整,能够消除对收集周期的感知。

分析器、堆转储和详细的GC日志记录工具对此有一定的帮助作用。再一次注意,这些都需要在真实场景的负载模式下进行监控。

有关不同垃圾收集器的更多信息,请查看这个指南。

https://stackify.com/what-is-java-garbage-collection/

JDBC性能

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

连接池

让我们从众所周知的事实开始,即数据库连接是昂贵的。连接池机制是解决这个问题非常重要的第一步。

这里建议使用HikariCP JDBC,这是一个非常轻量级(大约130Kb)并且速度极快的JDBC连接池框架。

JDBC批处理

持久化处理应尽可能地执行批量操作。JDBC批处理允许我们在单次数据库交互中发送多个SQL语句。

这样,无论是在驱动端还是在数据库端,性能都可能得到显著地提升。* PreparedStatement*是一个非常棒的的批处理命令,一些数据库系统(例如Oracle)只支持预处理语句的批处理。

另一方面,Hibernate则更加灵活,它允许我们只需修改一个配置即可快速切换为批处理操作。

语句缓存

语句缓存是另一种提高持久层性能的方法,这是一种鲜为人知但又容易掌握的性能优化方法。

只要底层的JDBC驱动程序支持,你就可以在客户端(驱动程序)或数据库端(语法树甚至执行计划)中缓存PreparedStatement。

规模的缩放

数据库复制和分片是提高吞吐量非常好的方法,我们应该充分利用这些经过实践检验的架构模式,以扩展企业应用的持久层。


架构改进

缓存

现在内存的价格很低,而且越来越低,从磁盘或通过网络来检索数据的性能代价仍然很高。缓存自然而然的变成了在应用程序性能方面不能忽视的关键。

当然,在应用的拓扑结构中引入一个独立的缓存系统确实会增加架构的复杂度,所以,应当充分利用当前使用的库和框架现有的缓存功能。

例如,大多数的持久化框架都支持缓存。Spring MVC等Web框架还可以使用Spring中内置的缓存支持,以及基于ETags的强大的HTTP级缓存。

横向扩展

无论我们在单个实例中准备了多少硬件,都会有不够用的时候。简而言之,扩展有着天生的局限性,当系统遇到这些问题时,横向扩展是处理更多负载的唯一途径。这一步肯定会相当的复杂,但却是扩展应用的唯一办法。

对大多数的现代框架和库来说,这方面还是支持得很好的,而且会变得越来越好。Spring生态系统有一个完整的项目集,专门用于解决这个特定的应用程序架构领域,其他大多数的框架也都有类似的支持。

除了能够提升Java的性能,通过集群进行横向扩展也有其他的好处,添加新的节点能产生冗余,并更好的处理故障,从而提高整个系统的可用性。


结论

在这篇文章中,我们围绕着提升Java应用的性能探讨了许多概念。我们首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践。最后,我们研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整。



欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢:

已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 20 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

兄弟,艿一口,点个赞!????

樽海鞘优化算法详解:原理、实现与应用 本文详细介绍了樽海鞘优化算法(SSA)的原理、实现与应用。该算法模拟樽海鞘链式群体行为,将优化问题解空间映射为群体移动空间,通过领导者引导和追随者跟随的协作机制寻找最优解。文章从生物特性、数学模型到Python代码实现进行了系统阐述,包括种群初始化、位置更新公式和收敛因子等核心要素,并展示了算法在函数优化等领域的应用潜力。该算法具有结构简单、参数少、实现容易等特点,在多种优化问题上表现良好。 阅读详情

相关推荐

RDM协议控制端实战:从数据包构造到高效设备发现算法

本文深入探讨了RDM协议控制端的核心开发流程,涵盖从数据包构造、发送到响应解析的全过程。重点解析了如何利用二分搜寻算法高效实现网络设备发现,并分享了组包、解包中的关键代码实现与常见调试技巧,为舞台灯光与智能硬件领域的嵌入式开发提供实战指导。

week9的博客 460

java replaceall正则表达式_编写高性能Java代码的最佳实践

作者:Eugen Paraschiv翻译:雁惊寒https://dzone.com摘要:本文首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践。最后研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整。以下是译文。介绍在这篇文章中,我们将讨论几个有助于提升Java应用程序性能的方法。我们首先将介绍如何定义可度量的性能指标,然后看看有哪些工...

weixin_39928787的博客 2368

DRAM自测试模块

1 背景技术DDR3/4 SDRAM属于高速外设,最高时钟频率可达到1600MHz,最高速率可达3200MT/s。在存储控制器与DDR34 SDRAM设备之间有一层专为高速接口设计的模拟电路,此电路对于前端设计人员来说,不管在模拟验证阶段和回片测试阶段,都是一个黑盒子。而对于后端测试人员来说若此部分为外购IP,则内部结构也是无法清楚了解的。所以在测试阶段,对于DDR34 SDRAM的测试就具有一定的

Hierro的DDR世界 7014

TestLatency反应测试游戏完整源代码

    一个非常简单的测试个人反应的游戏源代码//-----------------------------//GameScreen.java//-----------------------------package src;import javax.microedition.lcdui.*;import java.util.*;public class GameScreen exten

norains的专栏 3713

Linux蓝牙开发实战:5分钟搞懂BlueZ与D-Bus通信原理(附D-Feet调试技巧)

本文深入解析Linux下BlueZ蓝牙协议栈与D-Bus通信的核心机制,提供实战开发指南。通过D-Bus对象模型、信号订阅等关键技术点的详细讲解,配合D-Feet工具的高效调试技巧,帮助开发者快速掌握蓝牙设备管理与性能优化方法,提升物联网开发效率。

3a9bq4r8t2y的博客 333

电工识图入门:从电气符号到控制逻辑的完整指南

电工图纸是电气工程领域的通用语言,无论是设备安装、线路检修还是系统设计,看不懂图纸就像航海没有海图。很多初学者面对复杂的电气符号和线路走向感到无从下手,实际上只要掌握正确的识图方法,就能从简单的照明电路逐步过渡到复杂的自动化控制系统。 本文将从最基础的电气符号和图纸类型讲起,通过实际图纸案例解析,带你建立完整的识图逻辑。你将学会如何区分主回路和控制回路,如何追踪电流路径,以及如何结合图纸进行故障排查。无论你是电工新手、设备维护人员还是相关专业学生,这套方法都能帮你快速提升图纸理解能力。 1. 电气图纸基础:

chengyixian7877的博客 338

JAVA 正则表达式& replaceAll

JAVA 正则表达式& replaceAll

kingwin28的博客 431

Visual SLAM算法笔记

最近在学习SLAM算法,这里作为阅读笔记记录和总结一下。对经典的单目SLAM/VO算法做了摘要和总结,比如Nister VO、MonoSLAM、PTAM、ORB-SLAM、LSD-SLAM、SVO、DSO等,对几个单目和IMU的融合算法也做了简单总结,比如MSCKF、OKVIS、IMU-Preintegration等。

MulinB的技术博客 3万+

Java中String对象的replaceAll方法调用性能优化小技巧

Java中String对象的replaceAll方法调用性能优化小技巧

极客星云-优快云博客 2267

STM32 ES8266上阿里云IOT MQTT实践【第七章】:ESP8266+MQTT上阿里云物联网平台实践

本篇文章讲述了如何使用stm32接入阿里云IOT,WiFi模块为ESP8266,可以实现发布以及订阅。

超级大洋葱的博客 2万+

fragstats:景观指数趋势分析

优快云@_养乐多_本文将介绍景观指数时间序列的趋势分析,包括趋势类型、斜率、截距等。以及景观指数突变分析所用的软件和 python 代码。结果如下图所示,

养乐多的博客 2863

Python 写Android App性能:入门到高级

Android性能测试是移动应用开发的关键部分。无论是响应时间,内存使用,CPU占用,还是电池消耗,性能的每个方面都直接影响到用户体验。在这篇文章中,我们将介绍如何使用Python进行Android性能测试,从入门到高级。

649

【stata基础代码大全 】 可独立完成一篇实证

用stata打开,直接替换自己需要的变量就行,小白友好❗️❗️❗️。八、八.中介效应检验:路径分析(三种方法)第二种方法即Sobel-Goodman检验。*(7)Winsor处理,剔除异常值。六、内生性问题解决方法(附具体模型)其三,工具变量:两阶段最小二乘。第三种方法即Bootstrap检验。二、Stata中数据的基本处理。*(5)生成行业、年度虚拟变量。PSM1————近邻匹配。七、调节效应检验:机制分析。*(2)剔除数据中的样本。*(4)给变量添加标签。*(3)生成新的变量。*(6)基本数据匹配。

J_Giser的博客 8027

编写高性能Java代码的最佳实践

编写高性能Java代码的最佳实践 转:http://www.iteye.com/news/32850

vikingwei2015的专栏 157

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

上一篇: 推荐 5 款好用的 REST API 工具
下一篇: 抖音、达达、得物、哈啰等企业在高可用、中台、前端的最佳实践
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值