Java性能权威指南-总结21

news2024/12/23 20:12:39

Java性能权威指南-总结21

  • Java EE性能调优
    • 对象序列化
      • transient字段
      • 覆盖默认的序列化
      • 压缩序列化数据

Java EE性能调优

对象序列化

不同系统间的数据交换可以使用XMLJSON和其他基于文本的格式。Java进程间交换数据,通常就是发送序列化后的对象状态。尽管序列化在Java中随处可见,但Java EE中还有两点需要重点考虑。

  • Java EE服务器间的EJB调用——远程EJB调用——通过序列化交换数据。
  • HTTP会话状态通过对象序列化的方式来保存,这让HTTP会话可以高可用。

JDK提供了默认的序列化对象机制,以实现SerializableExternalizable接口。实际上,默认序列化的性能还有提升的空间,但此时进行过早的优化的确不太明智。特定的序列化和反序列化代码需要很多时间编写,而且也比默认的序列化代码更难维护。编写正确的序列化代码会有一些棘手,试图优化代码也会增加出错的风险。

transient字段

一般来说,序列化的数据越少,改进性能所需的代价就越少。 将字段标为transient,默认就不会序列化了。类可以提供特定的writeobject()readobject()以处理这些数据。如果不需要这些数据,简单地将它标记为transient就足够了。

覆盖默认的序列化

writeobject()readobject()可以全面控制数据的序列化。序列化很容易出错。为了了解序列化优化的困难性,以一个表示位置的简单对象Point为例:

	public class Point inplements Serializable {
		private int x;
		private int y;
		...
	}

在测试的机器上,100000个这样的对象可以在133毫秒内序列化,在741毫秒内反序列化。但即便像这么简单的对象,性能——即便非常困难——也能改善。

	public class Point implements Serializable {
		private transient int x;
		private transient int y;
		...
	
		private void writeObject(ObjectOutputStream oos) throws IOException {
			oos.defaultWriteobject();
			oos.writeInt(x);
			oos.writeInt(y);
		}
		private void readobject(ObjectInputStream ois) throws IOException,ClassNotFoundException {
			ois.defaultReadobject();
			x= ois.readInt();
			y = ois.readInt();
		}
	}

在测试机器上序列化100000个这样的对象仍然要花费132毫秒,但反序列化只需要468毫秒——改善了30%。如果简单对象的反序列化占用了相当大一部分程序运行的时间,像这样优化就比较有意义。然而请当心,这会使得代码难以维护,因为字段被添加、移除了,等等。

到目前为止,代码更为复杂了,但功能上依然正确(且更快)。注意,将此技术应用到一般场景时务必要谨慎:

	public class TripHistory implements Serializable {
		private transient Point[] airportsVisited;
		....
		//注意,这段代码不正确!
		private void writeObject(ObjectoutputStream oos) throws IOException {
			oos.defaultwriteobject();
			oos.writeInt(airportsVisited.length);
			for (int i = 0; i < airportsVisited.length; i++) {
				oos.writeInt(airportsvisited[i].getx());
				oos.writeInt(airportsVisited[i].getY());
			}
		}
		
		private void readobject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
			ois.defaultReadobject();
			int length = ois.readInt();
			airportsVisited = new Point[length];
			for (int i = 0; i < length; i++) {
				airportsVisited[i]= new Point(ois.readInt(), ois.readInt());
			}
		}
	}

此处的字段airportsVisited是表示出发或到达的所有机场的数组,按照离开或到达它们的顺序排列。有些机场,像JFK,在数组中出现得比较频繁,SYD(目前)只出现过一次。

由于序列化对象引用的代价比较昂贵,所以上述代码要比默认的数组序列化机制快:在测试的机器上,100000个Point对象的数组序列化用时4.7秒,反序列化用时6.9秒。上述“优化”使得序列化只用了2秒,反序列化只用了1.7秒。

然而这段代码是不正确的。指定JFK位置的数组引用都指向相同的对象。这意味着,如果发现数据不正确而更改单个JFK,那数组中的所有引用都会受到影响(因为它们引用的是相同的对象)。

用上述代码反序列化数组时,这些JFK引用就会变为独立的、不同的对象。当某个对象更改时,就只有它发生改变,结果它的数据就不同于其他那些表示JFK的对象了。

这条原则非常重要,应该铭记于心,因为序列化的调优常常就是如何对对象的引用进行特殊处理。 做对了,序列化的性能可以获得极大提升;做错了,就会引入不易察觉的bug。鉴于此,来考察一下StockPriceHistory的序列化,看看如何优化序列化。以下是这个类的字段:

	public class StockPriceHistoryImpl implements StockPriceHistory {
		private String symbol;
		protected SortedMap<Date, StockPrice> prices = new TreeMap<>();
		protected Date firstDate;protected Date lastDate;
		protected boolean needsCalc = true;
		protected BigDecimal highPrice;
		protected BigDecimal lowPrice;
		protected BigDecimal averagePrice;
		protected BigDecimal stdDev;
		private Map<BigDecimal, ArrayList<Date> histogram;
		..
		public StockPriceHistoryImpl(String s, Date firstDate, Date lastDate) {
			prices = ...
		}
	}

当以给定标志s构造StockPriceHistoryImpl对象时,会创建和存储SortedMap类型的变量prices,键值为startend之间的所有股票价格的时间。构造函数也设置保存了firstDatelastDate。除此之外,构造函数没有设置任何其他字段,它们都是延迟初始化。当调用这些字段的getter方法时,getter会检查needsCalc是否为真。如果为真,就会立即计算这些字段的值。

计算包括创建histogram,它记录了该股票特定的收盘价出现在哪些天。histogram包含的BigDecimalDate对象的数据与prices中的相同,只是看待数据的方式不同。所有的延迟加载字段都可以由prices数组计算得来,所以它们都可以标记为transient,并且在序列化和反序列化时不需要为它们做额外的工作。这个例子比较简单,因为代码已经完成了字段的延迟初始化,因此在接收数据时,可以一直延迟初始化。即便字段要即刻初始化,也仍然可以将可计算字段标记为transient,而在readobject()方法中重新计算它们的值。

注意,上述做法也维护了priceshistogram对象之间的关系:重新计算histogram时,会将已存在的对象塞到新的map中。这种做法在绝大多数情况下都能收到优化效果,但有时也会降低性能。下表就是这种情况,该表显示了histogram对象有无transient字段时进行序列化和反序列化所花费的时间,以及序列化数据的大小。
在这里插入图片描述
目前来看,这个例子中的对象序列化和反序列化节约了大约15%的时间。但这个测试实际上没有在接收时重建histogram对象:对象只有在接收数据的代码首次对其进行访问时才会创建。

有些时候并不需要histogram对象;客户端可能只关心特定日子里的股价,而不是整个histogram。还有一些不常见的情况,比如如果总是需要histogram,且测试中计算所有的histogram用时超过了3.1秒,那么延迟初始化字段就确实会导致性能下降。

在这个例子中,计算histogram并不属于这种情况——这是一种非常快的操作。一般来说,重新计算数据片段的代价很少会高于序列化和反序列化数据。 但在代码优化时仍然需要考虑。这个测试实际上并不向系统外传播数据,只是在预先分配的字节数组中写数据和读数据,所以它只是衡量了序列化和反序列化所用的时间。另外,histogram字段标为transient也减少了13%的数据大小。通过网络传送数据时,这就变得非常重要了。

压缩序列化数据

上述两种方法引出了改善序列化代码性能的第3种方法:数据序列化之后再进行压缩,使得它可以更快地在慢速网络上传输。StockPriceHistoryCompress在序列化时对prices进行了压缩:

	public class StockPriceHistoryCompress implements StockPriceHistory, Serializable {
		private byte[] zippedPrices;
		private transient SortedMap<Date, StockPrice> prices;
		
		private void writeobject(ObjectoutputStream out) throws IOException {
			if (zippedPrices == null) {
				makeZippedPrices()
			}
			out.defaultWriteObject();
		}
		
		private void readobject(ObjectInputStream in) throws IOException, ClassNotFoundException {
			in.defaultReadObject();
			unzipPrices();
		}
		
		protected void makeZippedPrices() throws IOException {
			ByteArrayoutputStream bais = new ByteArrayOutputStream();
			GZIPOutputStream zip = new GZIPOutputStream(bais);
			ObjectoutputStream oos = new ObjectoutputStream(new BufferedoutputStream(zip));
			oos.writeObject(prices);
			oos.close();
			zip.close();
			zippedPrices = bais.toByteArray();
		}
		
		protected void unzipPrices() throws IOException, ClassNotFoundException {
			ByteArrayInputStream bais = new ByteArrayInputStream(zippedPrices);
			GZIPInputStream zip = new GZIPInputStream(bais);
			ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(zip));
			prices = (SortedMap<Date, StockPrice>) ois.readobject();
			ois.close();
			zip.close();
		}
	}

makeZippedPrices()prices序列化成字节数组后保存,然后通常在writeobject()中调用defaultwriteobject()进行序列化。(事实上,如果可以定制序列化,将zippedPrices数组变成transient直接序列化数组的长度和字节会好一些。不过这个代码示例要清楚一点,且简单一些也更好。)在反序列化时,操作反过来执行。

如果目标是序列化成字节流(就像原先的示例代码一样),这就是个糟糕的提议。这并不令人惊奇,因为压缩字节所需的时间大大超过了写入本地字节数组的时间。参见下表。
10000个对象序列化和反序列化、带压缩和不带压缩时所用时间的对比
在这里插入图片描述
表中最有趣的是最后一行。在该轮测试中,数据在发送前进行了压缩,但readobject()并没有调用unzipPrices(),而是依据需要,在客户端首次调用getPrice()时才调用该方法。readobject()不再调用unzipPrices()后,就只有几个BigDecimal对象需要反序列化,速度非常快。

在这个例子中,很可能会出现客户端永远不需要实际的股票价格的情况:客户端可能只需要调用getHighPrice()和类似的方法获取合计数据。如果所有方法都是只在需要时获取数据,那么延迟解压价格数据信息就能节省大量时间。如果对象可能需要持久化,延迟解压也会有用(比如,备份HTTP会话状态,以防应用服务器失败)。延迟解压既节约CPU时间(因为跳过了解压),也节约内存(因为压缩后的数据需要的内存空间更小)。

所以,即便应用在高速局域网络中运行——尤其当目标是节约内存而不是时间时——对序列化数据进行压缩并延迟解压也仍然很有用。如果序列化是为了在网络中传输,那任何数据压缩都会有益处。 下表同样是对10000个股票对象进行序列化,不过这次它将数据传向了另一个进程。这个进程可以是在同一个机器上,也可以在通过宽带连接访问的其他机器上。
10000个对象的网络传输时间对比
在这里插入图片描述
同一机器上的两个进程之间的网络通信是最快的——虽然通信数据会发送到操作系统层,但压根不用通过网络。即便在这种情况下,压缩数据和延迟解压的性能仍然是最快的(至少在这个测试中是如此——但小数据量还是会有所衰退)。可以预料的是,一旦网络速度比较慢,传输数据又有数量级上的差别,总的耗费时间就会有巨大的差别。

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

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

相关文章

记一次 .NET 使用 csreids 连接 Redis 超时问题

背景: 使用windows server 2016 , IIS 部署的程序运行一段时间后出现异常, Redis 部署在内网其他服务器; 通过windows 事件查看器发现一些错误日志 , CSRedis.Internal.IO.RedisSocketException: Connect to server timeout 大概意思为连接redis服务 超时; 错误 2023/6/29 11…

通过 Python 控制 AWG70001 发送任意波

0. 实验准备 泰克 AWG70001 一台电脑 一根网线 使用网线连接 AWG70001 和电脑&#xff0c;并且配置 IP 在同一网段下 1. 环境要求 vxi11 numpy struct matplotlib 没有的库可以使用下面的命令安装 pip install vxi11 pip install numpy pip install struct pip install matp…

苹果iphone如何备份整个手机 苹果怎么查备份的照片

手机上的数据变得越来越重要&#xff0c;大家也越来越注重数据安全。如果手机设备丢失的话&#xff0c;不仅是设备的丢失&#xff0c;还是数据的丢失。因此&#xff0c;备份数据就显得很重要。那么&#xff0c;iphone如何备份整个手机&#xff0c;苹果怎么查备份的照片&#xf…

C++day4

2、 #include <iostream>using namespace std;class Person { private:int age;int *p; public://无参构造Person():p(new int(89)){age 18;}//有参构造Person (int age,int num){this->age age;this-> p new int(num);}//拷贝构造函数(深度拷贝)Person(Person …

NTP时钟服务器推荐-国内时间服务器顶尖设备

电子钟时间服务器在物联网应用中起到了关键的作用&#xff0c;它能够为各种智能设备提供准确的时间参考&#xff0c;确保设备之间的协同工作和数据的准确传输。无论是智能家居、智能工厂还是智慧城市&#xff0c;电子钟时间服务器都是不可或缺的一部分。 一、产品卖点 时间服…

启动spring boot项目时加载配置文件报错的问题

最近把电脑重置了一下&#xff0c;然后重新安装各种软件&#xff0c;从gitee拉去项目到本地运行时居然启动报错了 Failed to load property source from file:/D:/program/IdeaProjects/layui/target/classes/application.yml 这是加载配置文件的时候失败了&#xff0c;提示一堆…

Multiple HTTP implementations were found on the classpath错误的解决方法

当我们的项目中集成了多个AWS相关Jar包时,有可能就会遇到这个错误: 错误信息: There is an issue with the connector Code: InvalidInput.InvalidConnectorConfiguration Message: The connector configuration is invalid. Message: Multiple HTTP implementations were f…

自定义MVC架构【上】

目录 一、前言 1.什么是MVC架构 2.使用MVC架构的好处 3.MVC架构与三层架构的区别 4.MVC架构的思路 二、自定义MVC 1.最初版本 2.进阶版 3.反射优化版 4.反射增强版 一、前言 1.什么是MVC架构 MVC架构&#xff08;Model-View-Controller&#xff09;&#xff0c;即模…

【MSP432电机驱动设计—下篇】霍尔编码器测车轮运行距离与M/T综合公式法测速概念

开发板型号为MSP432P401r 今日得以继续我的MSP432电赛速通之路&#xff0c;本篇使用MSP432编程学习霍尔编码器M/T公式法测速概念&#xff0c;最终实现用外部中断方式测得小车行走路程&#xff0c;文章学习讲解原理、附上实例实践、附上关键代码、附上…

微信月活破10亿,安全性靠谁来支撑?

&#x1f449;腾小云导读 微信作为月活过10亿的国民级应用&#xff0c;其安全能力备受关注。值得注意的是&#xff0c;没有足够的特征数据&#xff0c;安全策略将是"无根之木&#xff0c;无源之水"。微信安全数据仓库作为安全业务的特征数据存储中心&#xff0c;每天…

【C/C++】解析 类成员属性

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

JVM类加载双亲委派-JVM(二)

上篇文章说了java类的加载&#xff0c;验证、准备、解析、初始化。类的初始化必须是类加载完才执行&#xff0c;所以类的构造方法初始化是在静态方法之后执行。 JVM类加载机制-JVM&#xff08;一&#xff09; 一、类加载和双亲委派机制 前面类加载主要通过类加载器实现&…

JavaWeb小记——登录、注册

目录 登录项目 步骤 1.首先写一个前台页面&#xff0c;一个form表单——login.jsp 2.后台获取前台输入的参数&#xff0c;并作校验 3.写一个JDBC工具类&#xff0c;用来连接数据库&#xff0c;本案例使用Druid连接池 4.写一个登录工具类&#xff0c;在数据库中查询数据&…

【云原生】k8s的pod基础(上)

1.pod的相关知识 1.1 pod的基础概念 pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是用绕着Pod来进行支撑和扩展Pod功能的&#xff0c;例如&#xff0c;用于管…

C++面试需求

一.auto 1.介绍&#xff1a; auto 是一个用于类型推导的关键字。它允许变量的类型在编译时根据其初始化值自动推断 例子&#xff1a; &#xff08;1&#xff09;自动类型推导变量 // C program to demonstrate working of auto // and type inference#include <bits/std…

shell正则入门

1.系统通配符 * #所有 . #当前目录 .. #当前目录的上一级目录 - #当前目录的上一次所在的目录 ~ #家目录 # #注释&#xff0c;超级管理员的命令行提示符 $ #引用变量&#xff0c;普通用户的命令行提示符 ? #匹配任意…

Spring Cloud Alibaba体系使用Nacos作为服务注册发现与配置中心

文章目录 Nacos介绍服务注册发现Nacos Discovery引入Nacos DiscoveryProvider和Consumer示例ProviderConsumer Nacos Discovery Starter其他配置选项 服务注册发现Nacos Config引入Nacos Config快速接入配置自动刷新profile粒度控制自定义namespace配置支持自定义Group支持自定…

UE5.2 LyraDemo源码阅读笔记(二)

UE5.2 LyraDemo源码阅读笔记&#xff08;二&#xff09; 创建了关卡中的体验玩家Actor和7个体验玩法入口之后。 接下来操作关卡中的玩家与玩法入口交互&#xff0c;进入玩法入口&#xff0c;选择进入B_LyraFrontEnd_Experience玩法入口&#xff0c;也就是第3个入口。触发以下请…

vue进阶-elementPlus

Element Plus官网 Element Plus 基于 Vue 3&#xff0c;面向设计师和开发者的组件库。减少开发者关注css&#xff0c;重心关注业务逻辑。 1. 入门 1.1 安装 npm install element-plus --save1.2 快速开始 1、main.js 引入并 use element-plus import { createApp } from …

C#,数值计算——灰色码(Gray Codes)的计算方法与源代码

一个 n位灰色码 序列&#xff0c;就是2的n次方 个 整数&#xff1b; 第一个数字为0&#xff1b; 相邻两个数字的二进制只有一位不一样&#xff1b; 第一个数字和最后一个数字的二进制也只有一位不一样。 using System; namespace Legalsoft.Truffer { /// <summary&…