90 Java调优

面试宝典:
https://gitee.com/angzihao/note

有哪些参考因素可以体现系统的性能?
CPU:例如,代码递归导致的无限循环,正则表达式引起的回溯,JVM 频繁的 FULL GC,以及多线程编程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙。
内存::内存成本要比磁盘高,相比磁盘,内存的存储空间又非常有限。所以当内存空间被占满,对象无法回收时,就会导致内存溢出、内存泄露等问题。
异常:Java 应用中,抛出异常需要构建异常栈,对异常进行捕获和处理,这个过程非常消耗系统性能。
数据库:大部分系统都会用到数据库,而数据库的操作往往是涉及到磁盘 I/O 的读写。大量的数据库读写操作,会导致磁盘 I/O 性能瓶颈,进而导致数据库操作的延迟性。
锁竞争

03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据

。在 Java 中,通常有两种创建字符串对象的方式,一种是通过字符串常量的方式创建,如 String str=“abc”;另一种是字符串变量通过 new 形式的创建,如 String str = new String(“abc”)。当代码中使用第一种方式创建字符串对象时,JVM 首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。String str = new String(“abc”) 这种方式,首先在编译类文件时,"abc"常量字符串将会放入到常量结构中,在类加载时,“abc"将会在常量池中创建;其次,在调用 new 时,JVM 命令将会调用 String 的构造函数,同时引用常量池中的"abc” 字符串,在堆内存中创建一个 String 对象;最后,str 将引用 String 对象。
String 有一个intern() 方法,native,用来检测在String pool是否已经有这个String存在。

Java 中要比较两个对象是否相等,往往是用 ==,而要判断两个对象的值是否相等,则需要用 equals 方法来判断。

在每次赋值的时候使用 String 的 intern 方法,如果常量池中有相同值,就会重复使用该对象

String a =new String("abc").intern();
String b = new String("abc").intern();
        
if(a==b) {
    System.out.print("a==b");
}

由于 ArrayList 是数组实现的,而数组是一块连续的内存空间,在添加元素到数组头部的时候,需要对头部以后的数据进行复制重排,所以效率很低;而 LinkedList 是基于链表实现,在添加元素的时候,首先会通过循环查找到添加元素的位置,如果要添加的位置处于 List 的前半段,就从前往后找;若其位置处于后半段,就从后往前找。因此 LinkedList 添加元素到头部是非常高效的。同上可知,ArrayList 在添加元素到数组中间时,同样有部分数据需要复制重排,效率也不是很高;LinkedList 将元素添加到中间位置,是添加元素最低效率的,因为靠近中间位置,在添加元素之前的循环查找是遍历元素最多的操作。而在添加元素到尾部的操作中,我们发现,在没有扩容的情况下,ArrayList 的效率要高于 LinkedList。这是因为 ArrayList 在添加元素到尾部的时候,不需要复制重排数据,效率非常高。而 LinkedList 虽然也不用循环查找元素,但 LinkedList 中多了 new 对象以及变换指针指向对象的过程,所以效率要低于 ArrayList。

三种遍历List的方式底层分析
1.for循环是通过get(i)实现的,对于ArrayList来说,其实就是下标访问,时间复杂度为O(n),对于ArrayList来说,其底层是双向链表,每次都要从链表头或者表尾去遍历.
2.Iterator
LinkedList的迭代器维护一个当前访问的数据的指针,找下一个元素把指针指向next即可,所以速度很快.
3.foreach底层其实也是通过迭代器实现的,

06 | Stream如何提高遍历集合效率?

官方将 Stream 中的操作分为两大类:中间操作(Intermediate operations)和终结操作(Terminal operations)。中间操作只对操作进行了记录,即只会返回一个流,不会进行计算操作,而终结操作是实现了计算操作。中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响,后者是指该操作只有拿到所有元素之后才能继续下去。终结操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作,前者是指遇到某些符合条件的元素就可以得到最终结果,后者是指必须处理完所有元素才能得到最终结果。操作分类详情如下图所示:
在这里插入图片描述

将中间操作称为懒操作,也正是由这种懒操作结合终结操作、数据源构成的处理管道(Pipeline),实现了 Stream 的高效。

07 | 深入浅出HashMap的设计与优化

几款常用的性能测试工具

1.ab(ApacheBench)
安装 在 Linux 系统中输入 yum-y install httpd-tools 命令
例如,一个测试并发用户数为 10、请求数量为 100 的的 post 请求输入如下:

ab -n 100  -c 10 -p 'post.txt' -T 'application/x-www-form-urlencoded' 'http://test.api.com/test/register'

post.txt 为存放 post 参数的文档,存储格式如下:

usernanme=test&password=test&sex=1

-n:总请求次数(最小默认为 1);
-c:并发次数(最小默认为 1 且不能大于总请求次数,例如:10 个请求,10 个并发,实际就是 1 人请求 1 次);
-p:post 参数文档路径(-p 和 -T 参数要配合使用);
-T:header 头内容类型(此处切记是大写英文字母 T)。

输出中,有几项性能指标可以提供给你参考使用:Requests per second:吞吐率,指某个并发用户数下单位时间内处理的请求数;Time per request:上面的是用户平均请求等待时间,指处理完成所有请求数所花费的时间 /(总请求数 / 并发用户数);Time per request:下面的是服务器平均请求处理时间,指处理完成所有请求数所花费的时间 / 总请求数;Percentage of the requests served within a certain time:每秒请求时间分布情况,指在整个请求中,每个请求的时间长度的分布情况,例如有 50% 的请求响应在 8ms 内,66% 的请求响应在 10ms 内,说明有 16% 的请求在 8ms~10ms 之间。

在循环迭代次数较少的情况下,常规的迭代方式性能反而更好;在单核 CPU 服务器配置环境中,也是常规迭代方式更有优势;而在大数据循环迭代中,如果服务器是多核 CPU 的情况下,Stream 的并行迭代优势明显。所以我们在平时处理大数据的集合时,应该尽量考虑将应用部署在多核 CPU 环境下,并且使用 Stream 的并行迭代方式进行处理。

08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?

在这里插入图片描述
传统 I/O 的性能问题

  1. 多次内存复制
  2. 阻塞:如果没有数据就绪,这个读取操作将会一直被挂起,用户线程将会处于阻塞状态。
    如何优化 I/O 操作
    1. 使用缓冲区优化读写流操作
    NIO 是面向 Buffer。Buffer 可以将文件一次性读入内存再做后续处理,而传统的方式是边读文件边处理数据。NIO 与传统 I/O 不同,它是基于块(Block)的,它以块为基本单位处理数据。
    2. 避免阻塞,优化 I/O 操作
    通道和多路复用器这两个基本组件实现了 NIO 的非阻塞。
    通道(Channel)
    传统 I/O 的数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。最开始,在应用程序调用操作系统 I/O 接口时,是由 CPU 完成分配,这种方式最大的问题是“发生大量 I/O 请求时,非常消耗 CPU“。
    通道的出现解决了以上问题,Channel 有自己的处理器,可以完成内核空间和磁盘之间的 I/O 操作。在 NIO 中,我们读取和写入数据都要通过 Channel,由于 Channel 是双向的,所以读、写可以同时进行。

多路复用器(Selector)
一个线程使用一个 Selector,通过轮询的方式,可以监听多个 Channel 上的事件。我们可以在注册 Channel 时设置该通道为非阻塞,当 Channel 上没有 I/O 操作时,该线程就不会一直等待了,而是会不断轮询所有 Channel,从而避免发生阻塞。

多路复用IO与NIO
字符流与字节流的区别
经过以上的描述,我们可以知道字节流与字符流之间主要的区别体现在以下几个方面:

字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
字节流默认不使用缓冲区;字符流使用缓冲区。
字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

dubbo原理和机制

09 | 网络通信优化之序列化:避免使用Java序列化

Java 序列化
在实现了 Serializable 接口的类的对象中,会生成一个 serialVersionUID 的版本号,这个版本号有什么用呢?它会在反序列化过程中来验证序列化对象是否加载了反序列化的类,如果是具有相同类名的不同版本号的类,在反序列化中是无法获取对象的。

Java 序列化的缺陷
1. 无法跨语言
2. 易被攻击

对象是通过在 ObjectInputStream 上调用 readObject() 方法进行反序列化的,将类路径上几乎所有实现了 Serializable 接口的对象都实例化。
对于需要长时间进行反序列化的对象,不需要执行任何代码,也可以发起一次攻击。攻击者可以创建循环对象链,然后将序列化后的对象传输到程序中反序列化,这种情况会导致 hashCode 方法被调用次数呈次方爆发式增长, 从而引发栈溢出异常。
3. 序列化后的流太大
4. 序列化性能太差

Java 序列化中的编码耗时要比 ByteBuffer 长很多。

Protocol Buffers
Protocol Buffers 是一种轻便高效的结构化数据存储格式。它使用 T-L-V(标识 - 长度 - 字段值)的数据格式来存储数据,T 代表字段的正数序列 (tag),Protocol Buffers 将对象中的每个字段和正数序列对应起来,对应关系的信息是由生成的代码来保证的。在序列化的时候用整数值来代替字段名称,于是传输流量就可以大幅缩减;L 代表 Value 的字节长度,一般也只占一个字节;V 则代表字段值经过编码后的值。这种数据格式不需要分隔符,也不需要空格,同时减少了冗余字段名。

10 | 网络通信优化之通信协议:如何优化RPC网络通信?

RPC 通信是大型服务框架的核心
微服务的核心是远程通信和服务治理。远程通信提供了服务之间通信的桥梁,服务治理则提供了服务的后勤保障。
微服务框架中的服务通信是基于 RPC 通信实现的,在没有进行组件扩展的前提下,SpringCloud 是基于 Feign 组件实现的 RPC 通信(基于 Http+Json 序列化实现),Dubbo 是基于 SPI 扩展了很多 RPC 通信框架,包括 RMI、Dubbo、Hessian 等 RPC 通信框架(默认是 Dubbo+Hessian 序列化)。

什么是 RPC 通信

架构的演变史
在这里插入图片描述
一个高并发场景下的 RPC 通信优化路径

1. 选择合适的通信协议
传输协议有 TCP、UDP 协议,这两个协议都是基于 Socket 编程接口之上
在这里插入图片描述
基于 TCP 协议实现的 Socket 通信是有连接的,而传输数据是要通过三次握手来实现数据传输的可靠性,且传输数据是没有边界的,采用的是字节流模式。基于 UDP 协议实现的 Socket 通信,客户端不需要建立连接,只需要创建一个套接字发送数据报给服务端,这样就不能保证数据报一定会达到服务端,所以在传输数据方面,基于 UDP 协议实现的 Socket 通信具有不可靠性。UDP 发送的数据采用的是数据报模式,每个 UDP 的数据报都有一个长度,该长度将与数据一起发送到服务端。

2. 使用单一长连接
服务之间的通信,连接的消费端不会像客户端那么多,但消费端向服务端请求的数量却一样多,我们基于长连接实现,就可以省去大量的 TCP 建立和关闭连接的操作,从而减少系统的性能消耗,节省时间。

3. 优化 Socket 通信
实现非阻塞 I/O:多路复用器 Selector 实现了非阻塞 I/O 通信。

高效的 Reactor 线程模型:Netty 使用了主从 Reactor 多线程模型,服务端接收客户端请求连接是用了一个主线程,这个主线程用于客户端的连接请求操作,一旦连接建立成功,将会监听 I/O 事件,监听到事件后会创建一个链路请求。链路请求将会注册到负责 I/O 操作的 I/O 工作线程上,由 I/O 工作线程负责后续的 I/O 操作。利用这种线程模型,可以解决在高负载、高并发的情况下,由于单个 NIO 线程无法监听海量客户端和满足大量 I/O 操作造成的问题。

串行设计:服务端在接收消息之后,存在着编码、解码、读取和发送等链路操作。如果这些操作都是基于并行去实现,无疑会导致严重的锁竞争,进而导致系统的性能下降。为了提升性能,Netty 采用了串行无锁化完成链路操作,Netty 提供了 Pipeline 实现链路的各个操作在运行期间不进行线程切换。

零拷贝:一个数据从内存发送到网络中,存在着两次拷贝动作,先是从用户空间拷贝到内核空间,再是从内核空间拷贝到网络 I/O 中。而 NIO 提供的 ByteBuffer 可以使用 Direct Buffers 模式,直接开辟一个非堆物理内存,不需要进行字节缓冲区的二次拷贝,可以直接将数据写入到内核空间。

除了以上这些优化,我们还可以针对套接字编程提供的一些 TCP 参数配置项,提高网络吞吐量,Netty 可以基于 ChannelOption 来设置这些参数。
TCP_NODELAY:TCP_NODELAY 选项是用来控制是否开启 Nagle 算法。Nagle 算法通过缓存的方式将小的数据包组成一个大的数据包,从而避免大量的小数据包发送阻塞网络,提高网络传输的效率。我们可以关闭该算法,优化对于时延敏感的应用场景。

SO_RCVBUF 和 SO_SNDBUF:可以根据场景调整套接字发送缓冲区和接收缓冲区的大小。

SO_BACKLOG:backlog 参数指定了客户端连接请求缓冲队列的大小。服务端处理客户端连接请求是按顺序处理的,所以同一时间只能处理一个客户端连接,当有多个客户端进来的时候,服务端就会将不能处理的客户端连接请求放在队列中等待处理。SO_KEEPALIVE:当设置该选项以后,连接会检查长时间没有

4. 量身定做报文格式

5. 编码、解码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值