Java性能权威指南-总结26

Java性能权威指南-总结26

  • 数据库性能的最佳实践
    • 异常
    • 日志

数据库性能的最佳实践

异常

Java的异常处理一直有代价高昂的坏名声。其代价确实比处理正常的控制流高一些,不过在大多数情况下,这种代价并不值得浪费精力去绕过。另一方面,因为异常处理是有成本的,所以不应将其用作一种通用机制。这里的指导方针是,根据良好程序设计的一般原则来使用异常:基本上,代码仅应该通过抛出异常来说明发生了意料之外的情况。遵循良好的代码设计原则,意味着Java代码不会因异常处理而变慢。

有两个因素会影响异常处理的一般性能。一个是代码块本身:创建一个try-catch块代价高吗?尽管很久以前可能是这样,但是近几年来,情况已非如此。不过在互联网上有些信息会留存很久,所以偶尔还会看到有人建议避免使用异常,因为try-catch块代价较高。这些建议都是老黄历了,因为现代JVM生成的代码可以非常高效地处理异常。

第二个方面是,(大部分)异常会涉及获取该异常发生时的栈轨迹信息。这一操作代价可能会很高,特别是在栈的轨迹很深时。

下面看一个例子。假如现在有一个特定方法的3种实现:

	public ArrayList<String> testSystemException() {
		ArrayList<String> al = new ArrayList<>();
		for (int i = 0; i < numTestLoops; i++) {
			Object o = null;
			if((i % exceptionFactor) != θ) {
				o = new Object();
			}
			try {
				al.add(o.tostring());
			} catch(NullPointerException npe) {
				//继续获取下一个字符串
			}
		}
		return al;
	}

	public ArrayList<String> testCodeException() {
		ArrayList<String> al = new ArrayList>();
		for (int i = 0; i < numTestLoops; i++) {
			try {
				if ((i% exceptionFactor) == θ) {
					throw new NullPointerException("Force Exception");
					}
					Object o = new Object();
					al.add(o.tostring());
				} catch(NullPointerException npe){
				//继续获取下一个字符串
				}
			}
		return al;
	}
	
	public ArrayList<String> testDefensiveProgramming() {
		ArrayList<String> al = new ArrayList<>();
		for (int i = θ;i < numTestLoops; i++) {
			Object o = null;
			if((i% exceptionFactor)!=θ) {
				o = new Object();
				}
			if (o != null) {
				al.add(o.tostring());
				}
			}
		return al;
	}

每个方法都返回一个字符串数组,其元素是从新创建的对象得到的。数组的大小会变化,跟抛出异常的次数有关。
下表列出了在最坏情况下(即exceptionFactor为1,每次迭代都会生成异常,得到的结果是一个空列表)为100000次迭代执行每个方法的时间。示例代码中,有的方法栈轨迹很浅(当调用这个方法时,栈上只有3个类),有的栈轨迹很深(当调用这个方法时,栈上有100个类)。

100%产生异常时的处理时间
在这里插入图片描述

这里有3点差别。首先,在每次迭代显式地构建异常的代码中,栈较浅和栈较深两种情况下时间差别很大。构建栈轨迹需要时间,这个时间和栈的深度有关。

第二个差别在这两种情况之间:代码显式地创建异常,或者是当JVM解析到空指针时创建异常(见表中的前两行)。目前的情况是,在某一个时刻,编译器会优化掉系统生成的异常;JVM开始重用同一个异常对象,而不是每次需要时创建一个新的。不管调用栈是什么样的,相关的代码每次执行时都会重用这个对象;而且这个异常实际上没有包含调用栈(也就是说,printStackTrace()没有输出)。这种优化在完整的栈异常信息抛出很长一段时间之后才会出现,所以如果测试用例中没有包含足够长的热身周期,是不会看到这种效果的。

最后,在访问对象之前先判断一下是否为null,这种防御性编程性能最好。在这个例子中,这一点并不意外,因为整个循环变成了空操作。所以对这个数字要持保留态度。尽管这些实现存在一些差别,但是请注意,大部分情况下,所用的时间都很少,是毫秒级的。平均到100000次调用,每次调用的执行时间几乎看不到什么差别。

如果异常使用得当,这些循环中的异常数目就会非常小。下列出了执行100000次循环时,产生1000次异常(1%的几率)需要的时间。
在这里插入图片描述

现在toString()方法的处理时间成了计算的大头。在栈较深的情况下,创建异常仍然有性能损失,不过提前测试null值的收益都被抵消了。

所以异常使用不当所带来的性能损失并没有想象的那么大。有些情况下,仍然会遇到创建太多异常的代码。因为性能损失主要来自填充栈轨迹信息,因此可以使用-XX:-StackTraceInThrowable标志(默认为false)来禁止生成栈轨迹信息。

这并不是个好主意:栈轨迹的存在就是为帮我们分析哪里出问题的。如果使用了-xx:-StackTraceInThrowable标志,也就丢失了这种能力。而且有些代码实际上会检查栈轨迹,并以此确定如何从异常恢复。(CORBA的参考实现就是这么工作的。)这种方式本身就有问题,但关键还在于禁止栈跟踪信息会使代码出现莫名其妙的问题。

JDK中有些API的异常处理会导致性能问题。当集合中并不存在要检索的元素时,很多集合类就会抛出异常。比如Stack类,如果栈是空的,当调用pop()时,就会抛出EmptyStackException。这种情况下,先通过防御性编程方式检查一下栈的长度会好一些。(另一方面,和很多集合类不同的是,Stack类支持保存为null的对象,所以不能用pop()方法返回null来说明栈是空的。)

关于异常的不当使用,JDK中最臭名昭著的例子是类加载:当使用ClassLoader类的loadClass()方法加载某个找不到的类时,就会抛出ClassNotFoundException。这实际并不是一个异常条件。不要期望一个类加载器能知道如何加载应用中的每个类,这也是之所以会有类加载器的层次结构的原因了。

在一个存在大量类加载器的环境中,这意味着,在层次化的类加载器中搜索知道如何加载给定类的那个类加载器时,会有大量的异常。比如前面类加载的例子中,如果关闭栈轨迹信息,运行速度会提升3%。

不过,类加载只是个例外。那个例子是使用很长的classpath做的微基准测试,而且即便是在这样的条件下,每次调用的差别也是毫秒级的。

快速小结

  1. 处理异常的代价未必会很高,不过还是应该在适合的时候才用。
  2. 栈越深,处理异常的代价越高。
  3. 对于会频繁创建的系统异常,JVM会将栈上的性能损失优化掉。
  4. 关闭异常中的栈轨迹信息,有时可以提高性能,不过这个过程往往会丢失一些关键信息

日志

日志有很多种。GC会生成自己的日志语句。日志可以定向到一个单独的文件中,其大小可以由JVM管理。即便在生产代码中,GC日志(使用-XX:+PrintGCDetails标志开启)的开销也是非常低的,而当出现问题时,它们的好处非常大,所以GC日志应该一直打开。

Java EE应用服务器会生成一个访问日志,每当有请求时都会更新。这类日志的影响通常比较明显:不管在应用服务器上运行的是何种测试,关闭这类日志可以明显改进性能。根据我的经验,从诊断角度看,当出现问题时这些日志的帮助不是很大。不过在业务需求方面,这类日志往往非常关键,此时必须开启。

很多应用服务器都支持Apachemod_log_config标准,尽管它并非一个Java EE标准。它可以针对每个请求精确地指定想要记录的信息(不支持mod_log_config语法的服务器通常也会支持某种形式的定制)。这里的关键是,记录的信息应该尽可能少,同时仍要满足业务需求。日志的性能会受所写数据量的影响。

特别是在HTTP访问日志中(或者笼统地说,在任何种类的日志中),记录下所有的数字信息是个不错的主意:记录IP地址而不是主机名,记录时间戳(比如从Unix纪元到现在所经过的秒数)而不是字符串数据(比如“Monday, June 3,201317:23:00-0600”),诸如此类。尽量减少需要花时间和内存去计算的任何数据转换,以便使日志对系统的影响将至最低。转换后的数据总是可以通过对日志做后续处理来获得。
对于应用日志,需要记住3个基本原则。

第一,协调好要打日志的数据和所选级别(Level)之间的关系。JDK中有7个标准的日志级别,而且Logger实例一般默认配置为输出其中的3个级别(INFO及更高级别)。在项目中,这往往会导致混淆:INFO级别听上去好像应该非常常见,而且应该提供与应用流程相关的描述(“现在正在处理A任务”,“现在正在做B任务",等等)。特别是对于存在大量线程的可扩展应用(包括Java EE应用服务器)而言,这类日志多了会给性能带来不利影响(更不用说太多没什么用的日志信息带来的风险了)。要学会使用更低级别的日志语句。类似地,当把代码签入到组库中时,应该考虑的是项目使用者的需求,而不是作为开发者的需求。如果消息对最终用户或系统管理员没什么意义,那默认开启这些日志就没什么帮助。它们的“作用”不过是拖慢了系统(还会让最终用户迷惑不解)。

第二个原则是使用细粒度的Logger实例。对每个类的Logger实例进行配置可能会很繁琐,但这么做是值得的,因为能够更好地控制日志输出。在一个较小的模块中,让一组类共享一个Logger实例,是个不错的折中办法。要记住的关键一点是,如果生产环境变化很大,有些问题(特别是那些在高负载情况下出现的问题,或者是其他与性能有关的问题)很难重现。打开太多日志往往会改变环境,导致原来的问题不再复现。

因此,必须能够做到仅打开一小组代码的日志(至少最初能控制一小组FINE级别的日志语句,然后是控制更多FINERFINEST级别的),这样就不会影响代码的性能了。在这两个原则之间,应该能够支持在生产环境中生成信息的小子集,前提是不影响系统性能。无论如何这都是应该考虑的,原因在于:如果日志会让生产系统变慢,其管理员很可能不会开启日志;在这种情况下,如果系统确实变慢了,重现问题的可能性也小了。

第三个原则是,在向代码引入日志时,应该注意,很容易编写出带来意想不到的副作用的日志代码,即使这个日志并没有开启。这是可以说明“过早的优化”很不错的又一种情况:每当要打日志的信息包含方法调用、字符串连接或者其他任何形式的资源分配(比如为MessageFormat参数分配一个Object数组)时,记得使用isLoggable()方法。

快速小结

  1. 为帮助用户找出问题,代码应该包含大量日志,但是这些日志默认都应该是关闭的。
  2. 如果Logger实例的参数需要调用方法或者分配对象,那么在调用该实例之前,不要忘了测试日志级别。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/35254.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【面试】美团面试真题和答案

文章目录 前言1.线程池有几种实现方式&#xff1f;2.线程池的参数含义&#xff1f;3.锁升级的过程&#xff1f;4.i 如何保证线程安全&#xff1f;5.HashMap和ConcurrentHashMap有什么区别&#xff1f;6.Autowired和Resource区别&#xff1f;7.说说常用的设计模式8.Redis为什么这…

SpringBoot2+Vue2实战(十二)springboot一对一,一对多查询

新建数据库表 Course Data TableName("t_course") public class Course implements Serializable {private static final long serialVersionUID 1L;/*** id*/TableId(value "id", type IdType.AUTO)private Integer id;/*** 课程名称*/private String…

微信小程序制作 购物商城首页 【内包含源码】

1、实现效果 手机效果预览,这里的首页使用到了轮播图。页面图片数据可以替换成自己的数据。 2、开发者工具效果图 3、项目的目录结构 4、首页核心代码 4.1 index.js 这里用来存放数据,页面的数据。目前是假数据,也可以调用接口接收真实数据 // index.jsimport {request }…

【我的创作纪念日】关于某站的音频爬虫+GUI

文章目录 一、前言&机遇二、爬虫代码三、爬虫GUI四、文件打包五、结果展示未来可期 一、前言&机遇 许久没看私信内容&#xff0c;一上线就看到了官方的私信&#xff0c;我已经来到CSDN1024天啦&#xff01; 想到注册这个号的初衷是学习记录爬虫&#xff0c;后面渐渐变…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(7 月 3 日论文合集)

文章目录 一、检测相关(9篇)1.1 Federated Ensemble YOLOv5 - A Better Generalized Object Detection Algorithm1.2 Zero-shot Nuclei Detection via Visual-Language Pre-trained Models1.3 Federated Object Detection for Quality Inspection in Shared Production1.4 Comp…

【数据科学和可视化】反思十年数据科学和可视化工具的未来

数据科学在过去十年中呈爆炸式增长&#xff0c;改变了我们开展业务的方式&#xff0c;并让下一代年轻人为未来的工作做好准备。但是这种快速增长伴随着对数据科学工作的不断发展的理解&#xff0c;这导致我们在如何使用数据科学从我们的大量数据中获得可操作的见解方面存在很多…

Django的数据库配置、生成(创建)过程、写入数据、查看数据的学习过程记录

目录 01-配置数据库信息02-安装Python的MySQL数据库驱动程序 mysqlclient03-安装Mysql&#xff0c;并启动Mysql04-定义Django的数据库模型(定义数据表-编写models.py文件)05-按照数据的配置生成数据库(执行迁移命令)05-01-生成迁移执行文件05-02-执行数据库模型迁移 06-查看数据…

git bash 命令行反应慢、卡顿

1. 在Windows11的电脑上安装了git 后&#xff0c;鼠标右键打开git bash here&#xff0c;打开窗口缓慢&#xff0c;输入命令也慢的要死&#xff0c;如果安装git的时候选择在桌面创建图标&#xff0c;通过桌面图标打开也是一样的 2. 最简单的ls 命令&#xff0c;都要停顿半秒 3.…

m4a音频格式转换器:让音频轻松换装

大家有没有遇到这样的情况——你下载了一个很酷的音频文件&#xff0c;但是播放设备却说“不认识”这个格式&#xff1f;别担心&#xff01;现在有个超级厉害的工具可以帮你解决这个问题&#xff0c;它就是m4a音频格式转换器&#xff01;它能让你的音频文件变身&#xff0c;适应…

TiDB(2):TiDB架构特性

1 TiDB 整体架构 TiDB 集群主要包括三个核心组件&#xff1a;TiDB Server&#xff0c;PD Server 和 TiKV Server。此外&#xff0c;还有用于解决用户复杂 OLAP 需求的 TiSpark 组件和简化云上部署管理的 TiDB Operator 组件。 架构图解 1.1 TiDB Server TiDB Server 负责接收…

技术服务企业缺成本票,所得税高怎么解决?可有良策?

技术服务企业缺成本票&#xff0c;所得税高怎么解决&#xff1f;可有良策&#xff1f; 《税筹顾问》专注于园区招商、企业税务筹划&#xff0c;合理合规助力企业节税&#xff01; 技术服务型企业最核心的价值就是为客户提供技术支撑&#xff0c;而这类型的企业在税务方面面临的…

CSRF漏洞复现

目录 CSRF产生的条件CSRF漏洞分类CSRF漏洞危害CSRF漏洞检测CSRF漏洞修复方案利用靶场CSRF-Minefield-V1.0漏洞复现 CSRF产生的条件 一、被攻击者在登陆了web网页&#xff0c;并且在本地生成了cookie 二、在cookie未过期的情况下&#xff0c;利用同一个浏览器访问了攻击者的页…

最新版Flink CDC MySQL同步Elasticsearch(一)

1.环境准备 首先我们要基于Flink CDC MySQL同步MySQL的环境基础上&#xff08;flink-1.17.1、Java8、MySQL8&#xff09;搭建Elasticsearch7-17-10和Kibana 7.17.10。笔者已经搭建好环境&#xff0c;这里不做具体演示了&#xff0c;如果需要Es的搭建教程情况笔者其他博客 注意…

JVM源码剖析之Java对象创建过程

关于 "Java的对象创建" 这个话题分布在各种论坛、各种帖子&#xff0c;文章的水平参差不齐。并且大部分仅仅是总结 "面试宝典" 的流程&#xff0c;小部分就是copy其他帖子&#xff0c;极少能看到拿源码作为论证。所以特意写下这篇文章。 版本信息如下&…

Eclipse显示层级目录结构(像IDEA一样)

有的小伙伴使用IDEA习惯了&#xff0c;可能进入公司里面要求使用eclipse&#xff0c;但是eclipse默认目录是并列显示&#xff0c;而不是层级显示。部分人用起来感觉十分不方便。我们可以更改一下设置。 1、打开eclipse&#xff0c;找到这里 2、选择PackagePresentation 3、选…

Github-提交PR指南

1. Fork你将要提交PR的repo 2. 将你fork下来的repo克隆到你的本地 git clone your_repo.git Cloning into ultralytics... remote: Enumerating objects: 8834, done. remote: Counting objects: 100% (177/177), done. remote: Compressing objects: 100% (112/112), done. …

第二步:STM32F407ZGT6资源介绍

1.1 STM32F407ZGT6资源描述 内核&#xff1a; 32位 高性能ARM Cortex-M4处理器 时钟&#xff1a;高达168M,实际还可以超屏一点点 支持FPU&#xff08;浮点运算&#xff09;和DSP指令 IO口&#xff1a; STM32F407ZGT6: 144引脚 114个IO 大部分IO口都耐5V(模拟通道除外) …

C# .NET 如何调用 SAP RFC 接口

1.分析传参结构 SAP 传参格式对应 .NET 参数格式 SAP 参数.NET 参数参数类型import(导入)——关联类型为数据元素Param单个变量参数import(导出)——关联类型为结构体Struct结构体tableTable表 下面是 SAP 对应参数类型&#xff1a; 2.web.config 配置 配置文件需要客户端…

win10安装pytorch GPU

我记得以前安装过深度学习库GPU版本&#xff0c; 需要安装cuda什么的&#xff0c;翻了下还真写过一篇win10安装tensorflow的文章&#xff0c;但是流程不止不详细&#xff0c;还不清晰。这次就再记录一遍 这次安装的是pytorch&#xff0c;这么多年似乎pytorch要逐渐统一深度学习…

【算法与数据结构】232、LeetCode用栈实现队列

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;这道题要求我们用栈模拟队列&#xff08;工作上一定没人这么搞&#xff09;。程序当中&#xff0c;pus…