Java性能权威指南-总结26

news2024/10/5 14:24:14

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实例的参数需要调用方法或者分配对象,那么在调用该实例之前,不要忘了测试日志级别。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

添加 zabbix 客户端主机 自定义监控内容 自动发现与注册(得不到假装不想要)

文章目录 一、添加zabbix客户端主机2.关闭防火墙和修改主机名 二、自定义监控内容1.需求&#xff1a;限制登录人数不超过 3 个&#xff0c;超过 3 个就发出报警信息在客户端创建自定义 key在 Web 页面创建自定义监控项模板 三、zabbix 自动发现与自动注册zabbix 自动发现&#…

HCIE-datacom | 网络准入控制

一、前言 之前提供网络技术咨询服务时&#xff0c;有一位实习生同学向我咨询了有关网络准入的相关情景&#xff0c;在这里我结合华为HCIE-datacom中“网络准入控制”这一节等相关资料&#xff0c;对网络准入技术进行一下简单的理论性说明&#xff0c;与资料的讲解思路相同&…

基于matlab使用AprilTag标记进行相机校准(附源码)

一、前言 AprilTags被广泛用作物体检测、定位应用的视觉标记&#xff0c;并作为相机校准的目标。AprilTags类似于QR码&#xff0c;但旨在编码更少的数据&#xff0c;因此可以更快地解码&#xff0c;这对于实时机器人应用程序非常有用。使用 AprilTags 作为校准模式的优点包括更…

Docker学习笔记21

案例三&#xff1a;使用容器运行一个wordpress应用&#xff1a; 语言开发环境&#xff08;PHP&#xff09; 数据库 第一步&#xff1a;创建一个工程目录&#xff1a; mkdir wordpress cd wordpress 第二步&#xff1a;创建一个docker-compose.yaml文件&#xff1a; [rootnode…

Mybatis进阶(2)——为什么用mybatis? 多表查询解决 延迟加载 mybatis缓存 【未完待续】

目录 引出一、为啥用mybatis&#xff1f;二、多表查询之一对多【待续】1.一对多的情况方案一&#xff1a;采用多表联查的方式&#xff08;1&#xff09;resultMap&#xff08;2&#xff09;查询的java接口和xml的SQL语句&#xff08;3&#xff09;对应关系分析&#xff08;4&am…

使用docker搭建minio分布式对象存储系统

使用docker搭建minio分布式对象存储系统 这里我简单的和大家介绍一下什么是minio &#xff1f; 附上Minio官网链接&#xff1a;https://minio.org.cn/ MinIO是一种开源的对象存储服务器&#xff0c;通过使用标准的HTTP/REST API来访问和管理数据。它采用分布式架构&#xff0c…

【SAP UI5 控件学习】DAY02 Input组PartII

1. CheckBox控件 1.1 最普通的CheckBox <CheckBox text"Option a" selected"true" />如果需要设置选中状态&#xff0c;需要设置selected属性为true 1.2 部分选中CheckBox <CheckBox text"Option partially selected" selected"t…

【ElasticSearch】DSL查询语法

文章目录 1、DSL查询分类2、DSL基本语法3、全文检索查询4、精确查询5、地理查询6、复合查询--相关性打分算法7、复合查询之Function Score Query8、复合查询之BooleanQuery 1、DSL查询分类 Elasticsearch提供了基于JSON的DSL&#xff08;Domain Specific Language&#xff09;…

【ARM Coresight 系列文章 2 - ARM Coresight 介绍】

文章目录 1.1 ARM Coresight 介绍1.1.1 ARM Coresight 发展历史 1.2 ARM Coresight 框架介绍1.1.1 Trace 通路1.1.3 Debug 通路1.1.4 Trigger 通路 1.1 ARM Coresight 介绍 ARM Coresight是ARM公司提供的一种调试和跟踪技术&#xff0c;用于ARM处理器的调试和性能分析。它通过…

Java中规模软件开发实训——简单的文本编辑器(代码注释详解)

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;html css js&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;在现代社会中…

k8s如何伸缩应用程序和执行滚动更新

一、伸缩应用程序 我们创建了一个 Deployment (opens new window)&#xff0c;然后通过 服务 (opens new window)提供访问 Pod 的方式。我们发布的 Deployment 只创建了一个 Pod 来运行我们的应用程序。当流量增加时&#xff0c;我们需要对应用程序进行伸缩操作以满足系统性能…

第五章:L2JMobius学习 – 快速部署L2JMobius汉化版

L2JMobius是一套开源的 LineageII 的服务器端代码&#xff0c;使用Java语言编写。在前面的章节中&#xff0c;我们安装了mariadb10数据库以及jdk17运行环境&#xff0c;这两个是必须的。紧接着&#xff0c;我们又安装了eclipse开发工具&#xff0c;然后创建了“L2J_Mobius”Jav…

按键输入实验(stm32)

目录 按键的相关代码key.ckey.h LED的相关代码BEEP的相关代码beep.cbeep.h main.c代码的相关说明相关硬件说明实验结果 说明&#xff1a;以下内容参考正点原子资料 按键的相关代码 key.c void KEY_Init(void) //IO初始化 { GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2Peri…

ModaHub魔搭社区:可视化的AI原生云向量数据库 Milvus 2.2.9 :JSON、PartitionKey、Dynamic Schema

目录 新特性 功能增强 其他优化 问题修复 亮点颇多、精彩程度堪比大版本的 Milvus 2.2.9 来啦&#xff01; 随着 LLM 的持续火爆&#xff0c;众多应用开发者将目光投向了向量数据库领域&#xff0c;而作为开源向量数据库的领先者&#xff0c;Milvus 也充分吸收了大量来自社…

ROS:话题名称设置

目录 一、 前言二、rosrun设置话题重映射三、launch文件设置话题重映射四、编码设置话题名称4.1C4.1.1全局名称4.1.2相对名称4.1.3私有名称 4.2Python 实现4.2.1全局名称4.2.2相对名称4.2.3私有名称 一、 前言 在ROS中节点名称可能出现重名的情况&#xff0c;同理话题名称也可…

[攻防世界] [RE] [APK] app2

解题思路 导入jadx查看manifest.xml 查看主函数并未发现有价值的东西&#xff0c;于是查看manifest.xml中主函数下一个<activity> 截取FileDataActivity代码 package com.tencent.testvuln;import android.os.Bundle; import android.widget.TextView; import com.tence…

2022年真题 - 15 - 磁盘管理(vdo磁盘)

磁盘管理 - vdo磁盘 题目配置验证配置题目 StorageSrv - 磁盘管理 在 storagesrv 上新加一块 10G 磁盘;创建 vdo 磁盘,并开启 vdo 磁盘的重删和压缩;名字为 vdodisk,大小为150G,文件系统为 ext4;并设置开机自动挂载。挂载到 /vdodata。配置 新加一块 10G 磁盘; 安装…

驱动 作业 day4

编写LED灯的驱动&#xff0c;创建三个设备文件&#xff0c;每个设备文件和一个LED灯绑定&#xff0c;当操作这个设备文件时只能控制设备文件对应的这盏灯。 此时没有安装led2 和led3的驱动所以会打开设备文件失败 装完以后就可以正常控制了 以下是设备现象 head.h ubuntuu…

docker 的整体架构及各模块组件 《深入docker底层原理》

1.Docker 整体架构 Docker 是一个 C/S 模式的架构&#xff0c;后端是一个松耦合架构&#xff0c;模块各司其职。 1、用户是使用 Docker Client 与 Docker Daemon 建立通信&#xff0c;并发送请求给后者。 2、Docker Daemon 作为 Docker 架构中的主体部分&#xff0c;首先提供…

Windows如何设置自动关闭未响应的程序?Windows设置自动关闭未响应的程序方法,带图详解

Windows系统程序经常出现程序未响应现象&#xff0c;如何通过注册表使其自动关闭呢 1、首先快捷键winR唤出【运行】 输入regedit 2、确定后就打开了注册表编辑器&#xff0c;定位到【HKEY_CURREnT_UsER\Control panel\desktop】项下 3、在右侧找【AutoEndTasks】数值数据&#…