JavaWeb技术内幕二:Java IO工作机制

简介: 版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a724888/article/details/81416419 这位大侠,这是我的公众号:程序员江湖。
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a724888/article/details/81416419

微信公众号【Java技术江湖】一位阿里 Java 工程师的技术小站。(关注公众号后回复”Java“即可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的Java学习指南、Java程序员面试指南等干货资源)

IO问题是当今web应用所面临的主要问题之一,因为数据在网络中随处流动,在这个流动过程中都涉及IO问题,并且大部分应用的瓶颈都是IO瓶颈。

本章将从IO的角度出发,介绍IO类库的基本架构,磁盘IO的工作机制,网络IO的工作方式,以及socket和NIO等等。

Java的IO类库基本架构

IO问题无法回避,很容易成为性能瓶颈,因为IO设备的速度一般是很慢的。Java IO一直在做这方面的优化,1。4开始引入了NIO

数据格式:可以是字符流或者是字节流

基于字节操作的IO接口stream

基于字符操作的接口 writer和reader

传输方式:可以在网络中传输也可以和磁盘进行传输

基于磁盘操作的File

基于网络操作的socket

基于字节的IO操作接口

对于字节流来说,一般需要写入或读取字节数组,而这个写入或读取位置可能是文件,也可能是网络

基于字符的IO操作接口

实际上磁盘和网络传输都要转化成二进制字节流,之以所提供字符流接口是因为方便用户直接写入字符。

当然这其中还涉及到编码和解码的问题。

字符和字节的转化接口

为了实现字符到字节的相互转化,java提供了inputstreamreader和outputstreamreader两个类来实现转化,这个类通过装饰者
模式封装两个实例,从而完成转化操作。

但是转换过程中注意指定编码,要不然就乱码了,装饰者模式还支持把IO流包装成支持缓存,支持管道等特性IO流。

磁盘IO工作机制

几种访问磁盘的方式

1 我们知道,读写文件的IO操作需要执行操作系统提供的接口,因为磁盘是操作系统管理的,应用程序只能通过系统调用来工作。

写和读对应write和read系统调用。

2 由于操作系统执行系统调用可能会有上下文切换的问题,需要到内核空间运行,也就涉及到内核空间和用户空间的数据复制问题。

为什么要这样呢,因为操作系统为了保护自身安全,要把用户程序和内核空间分开,虽然保证安全性,但是却会降低速度。

3 由于IO本身非常耗时,所以为了弥合磁盘和内存的速度差,一般会使用缓存机制缓存一部分的磁盘文件。这样就可以避免每次IO都要经过磁盘。

标准访问文件方式(需要两个空间的数据复制)

标准IO就是使用read接口时,先访问内核缓存,未命中就访问磁盘,然后进行缓存。

使用write接口时,write接口用户空间复制到内核空间的缓存中,此时用户程序就会返回,至于什么时候把缓存内容写入磁盘则由操作系统来决定,除非我们显示地调用了sync命令进行同步调用。

用户空间缓存-->内核空间缓存-->物理磁盘

直接IO(不需要经过内核空间)

直接IO就是应用程序不需要经过内核空间,直接访问磁盘。这种方式的典型就是数据库,数据库知道该缓存哪些数据,可以做预加载,提高访问速度,这些处理对用户程序是透明的。

如果是由操作系统来缓存,是很难做到的。

但是这也有一些问题,就是缓存不命中时就会直接从磁盘加载,速度很慢,一般结合直接IO和异步IO来做,会比较高效。

同步IO

同步指的是读取和写入是同步的,只有数据读写成功后才会返回结果,需要程序等待,性能比较差。

异步IO

异步IO可以先执行其他任务,而不是阻塞等待,请求数据返回后才会继续执行下面的操作。

内存映射

内存映射指的是操作系统将某一块内存和磁盘中的文件关联起来,当要访问内存的一段数据时,转换为访问文件的某一段数据。

这种操作也可以避免数据从内核空间和用户空间间的复制

Java访问磁盘文件

Java的file用于定位资源,他不一定是实际文件,也可以是一个目录,甚至一个不存在的对象。

只有在真正读取file的时候才会检查它存不存在。

在打开文件的输入流inputstream时,会创建一个filedescription对象,代表Linux中对应的fd。Linux通过fd与磁盘进行交互。

在这里说下fd和inode的区别

inode 或i节点是指对文件的索引。如一个系统,所有文件是放在磁盘或flash上,就要编个目录来说明每个文件在什么地方,有什么属性,及大小等。就像书本的目录一样,便于查找和管理。这目录是操作系统需要的,用来找文件或叫管理文件。许多操作系统都用到这个概念,如linux, 某些嵌入式文件系统等。当然,对某个系统来说,有许多i节点。所以对i节点本身也是要进行管理的。

在linux中,内核通过inode来找到每个文件,但一个文件可以被许多用户同时打开或一个用户同时打开多次。这就有一个问题,如何管理文件的当前位移量,因为可能每个用户打开文件后进行的操作都不一样,这样文件位移量也不同,当然还有其他的一些问题。所以linux又搞了一个文件描述符(file descriptor)这个东西,来分别为每一个用户服务。每个用户每次打开一个文件,就产生一个文件描述符,多次打开就产生多个文件描述符,一一对应,不管是同一个用户,还是多个用户。该文件描述符就记录了当前打开的文件的偏移量等数据。所以一个i节点可以有0个或多个文件描述符。多个文件描述符可以对应一个i节点。

Java序列化技术

Java序列化就是把一个对象转换成一串二进制表示的字节数组,通过保存或转移这些数据来持久化。

序列化对象必须实现serializable接口。但是和class文件可以直接通过defineclass加载类不同,反序列化时字节码必须依据模板类进行反序列化。

所以我们应该看看序列化后的对象到底长啥样

实际上,序列化以后的数据主要包括这些内容(只列举重要的)

1 序列化协议

2 版本

3 class名字

4 域类型,弗雷信息,实际属性值等等。

网络IO工作机制

网络IO必须通过物理链路和通信协议进行连接。

TCP状态转化

影响网络传输的因素

1 网络带宽,一般受物理链路影响,比如光纤比双绞线快得多

2 传输距离,传输距离主要影响传输延时

3 TCP拥塞控制,为了实现拥塞控制,网络传输速度会受整体网络环境影响

Java socket工作机制

socket就是操作系统对TCP/IP协议栈的封装,以便用户程序进行为了编程。

建立通信链路

1 客户端建立socket,自动分配端口号,然后绑定远端地址和端口号。

2 执行connect方法,完成三次握手的前两次

执行accpet方法完成三次握手的第三次

3 服务端建立serversocket需要绑定端口号进行监听,调用accpet方法进入阻塞等待请求到了,连接到来时为其建立一个新的数据结构,此时这个数据结构还只是未完成的结构。

只有在它与客户端完成三次握手后socket新实例才被成功创建。

每个已完成三次握手的socket都被操作系统管理,对应着不同的本地主机ip+端口:远程主机+端口

数据传输

socket通过inputstream和outputstream传输数据,事实上,操作系统会为它们分配一定大小的缓冲区,数据的读取和写入都是通过这个缓冲区来完成的(NIO出现后可以让用户程序管理缓冲区)

这个缓冲区也被称作RecvQ队列和SendQ队列。当队列为空或满时,执行读和写操作会阻塞。

如果读和写同时发生,可能会造成死锁。

NIO的工作方式

BIO的挑战

1 BIO即阻塞IO,无论是磁盘还是网络IO,都会发生阻塞等待,线程会阻塞,等待IO响应时间很长,并且导致上下文切换,开销很大。

2 对于服务端,高并发访问时使用BIO显然不能被接受。如果一个线程对应一个客户端,可以避免影响其他线程工作,同时使用线程池降低线程创建开销。

3 但是有些场景仍然不能解决,比如需要大量HTTP长连接,比如几百万,这些连接不怎么需要IO操作,但是却需要保持连接,如果开启200w的线程,显然是不可能的。

另外,多线程读写共享数据时需要同步,非常麻烦。

而且多线程优先级不好控制

NIO的工作机制

Channel,buffer,selector,key。

Channel指的是IO访问对象,可以是File,也可以是socket
,通过channel再派生出socket

selector是选择器,基于底层的IO多路复用器实现。

buffer是缓存,用户可以自己控制IO的读缓存和写缓存。

key就是selector上注册的键,分别代表不同状态的IO,比如就绪,已连接,可读,可写。

通信过程:

1 selector工厂创建一个selector,创建一个channel,绑定到一个serversocket上。

2 设置serversocket为非阻塞

3 调用seletor的selectedkeys获得所有事件,判断是否就绪事件。

4 通过channel获取buffer,完成IO读写操作。

上述过程,一个线程负责监听就绪时间,一般是阻塞的while循环,一个线程负责处理就绪的IO请求。

由于这个特点,只需少量线程就可以完成大量的连接请求。

buffer的工作方式

buffer就是一个缓冲区,可以分配长度。
使用position,limit,capacity标识容量情况。
使用flip可以在读缓存和写缓存之间转换。

1 当然,使用buffer需要进行用户空间和内核空间的数据复制,所以比较耗时,buffer提供另一种方式directbuffer就是和底层存储空间直接关联的缓冲区,他通过jni直接操作非堆内存。

2 由于这部分内存直接分配在内核空间,所以不需要额外一次复制,所以执行的效率要更高。

3 jvm释放这部分非堆内存一般需要调用system.gc来显示释放,可能引起内存泄漏。

NIO的数据访问方式

NIO提供了比传统文件访问方式更好的方法,NIO有两个优化方法:一个是Filechannel.transferTo,一个是filechannel.map

1 filechannel.transferTo
该操作直接在内核空间移动数据,当然是用于写操作,不用于读操作。

2 filechannel.map将文件按照一定大小块映射成内存区域,实现了mmap。

IO调优

磁盘IO优化

性能检测

1 我们可以压测应用,看看IO的wait指标是否正常。
Linux下可以通过iostat查看IO状态

2 IOPS是IO性能的重要参数,要看看最低的IOPS是多少。

IOPS (Input/Output Operations Per Second),即每秒进行读写(I/O)操作的次数,多用于数据库等场合,衡量随机访问的性能。存储端的IOPS性能和主机端的IO是不同的,IOPS是指存储每秒可接受多少次主机发出的访问,主机的一次IO需要多次访问存储才可以完成。例如,主机写入一个最小的数据块,也要经过“发送写入请求、写入数据、收到写入确认”等三个步骤,也就是3个存储端访问。

3 RAID技术可以提升磁盘IO性能。每种RAID方案对IO性能提升不同,可以用raid因子来表示。

提升IO性能

1 增加缓存

2 优化磁盘管理系统,寻址策略,非常底层。。

3 设计索引,异步和非阻塞加快磁盘访问。

4 使用raid。

1 raid0平均写到多个磁盘阵列,读写都是并行的,速度翻倍

2 raid1实现了数据备份

3 raid5是0和1的折中,平均读写,但是留一盘用来备份和恢复。

4 raid0+1

TCP网络参数优化

1 端口号有65536个。

2 可用端口号不足时遇到大量并发请求时会成为瓶颈,大量请求等待建立连接。

如果出现大量time wait,可以设置timewait时间为更小值。

3 通过ab压测,发现time wait的连接很多,降低timeout时延,则timewait数量明显减少。

4 除了增大端口范围外,还可以让TCP连接复用等方式来提高性能。

网络IO优化

1 减少网络交互次数

可以合并多个请求为一个请求

2 减少网络传输数据量大小

压缩数据,尽量通过协议头来获取信息,设置使用代理时只判断协议头即可完成请求或者负载均衡。

3 尽量减少编码

直接使用字节流传输,减少了一次解码过程

4 IO方式

1 同步和异步

同步就是前后任务依次完成,互相依赖,异步则不依赖其他任务。

2 阻塞和非阻塞

阻塞和非阻塞主要和cpu有关,阻塞会切换cpu上下文,非阻塞则不会。

设计模式

适配器

IO接口在转换inputstream到reader时使用inputstreamreader作为适配器。

装饰者

inputstream是具体组件,filterinputstream和bufferedinputstream是装饰者

区别

适配器是将一个接口转变成另一个接口,主要实现了复用目的。而装饰者则是要保持原有接口,但是要增强其接口功能。

相关文章
|
12天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
45 7
|
2天前
|
存储 监控 安全
单位网络监控软件:Java 技术驱动的高效网络监管体系构建
在数字化办公时代,构建基于Java技术的单位网络监控软件至关重要。该软件能精准监管单位网络活动,保障信息安全,提升工作效率。通过网络流量监测、访问控制及连接状态监控等模块,实现高效网络监管,确保网络稳定、安全、高效运行。
27 11
|
12天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
13天前
|
Java 程序员
深入理解Java异常处理机制
Java的异常处理是编程中的一块基石,它不仅保障了代码的健壮性,还提升了程序的可读性和可维护性。本文将深入浅出地探讨Java异常处理的核心概念、分类、处理策略以及最佳实践,旨在帮助读者建立正确的异常处理观念,提升编程效率和质量。
|
14天前
|
Java 开发者 UED
深入探索Java中的异常处理机制##
本文将带你深入了解Java语言中的异常处理机制,包括异常的分类、异常的捕获与处理、自定义异常的创建以及最佳实践。通过具体实例和代码演示,帮助你更好地理解和运用Java中的异常处理,提高程序的健壮性和可维护性。 ##
38 2
|
14天前
|
Java 开发者
Java中的异常处理机制深度剖析####
本文深入探讨了Java语言中异常处理的重要性、核心机制及其在实际编程中的应用策略,旨在帮助开发者更有效地编写健壮的代码。通过实例分析,揭示了try-catch-finally结构的最佳实践,以及如何利用自定义异常提升程序的可读性和维护性。此外,还简要介绍了Java 7引入的多异常捕获特性,为读者提供了一个全面而实用的异常处理指南。 ####
38 2
|
21天前
|
Java
java 中 IO 流
Java中的IO流是用于处理输入输出操作的机制,主要包括字节流和字符流两大类。字节流以8位字节为单位处理数据,如FileInputStream和FileOutputStream;字符流以16位Unicode字符为单位,如FileReader和FileWriter。这些流提供了读写文件、网络传输等基本功能。
40 9
|
17天前
|
Java 程序员 UED
深入理解Java中的异常处理机制
本文旨在揭示Java异常处理的奥秘,从基础概念到高级应用,逐步引导读者掌握如何优雅地管理程序中的错误。我们将探讨异常类型、捕获流程,以及如何在代码中有效利用try-catch语句。通过实例分析,我们将展示异常处理在提升代码质量方面的关键作用。
27 3
|
17天前
|
Java 数据库连接 开发者
Java中的异常处理机制:深入解析与最佳实践####
本文旨在为Java开发者提供一份关于异常处理机制的全面指南,从基础概念到高级技巧,涵盖try-catch结构、自定义异常、异常链分析以及最佳实践策略。不同于传统的摘要概述,本文将以一个实际项目案例为线索,逐步揭示如何高效地管理运行时错误,提升代码的健壮性和可维护性。通过对比常见误区与优化方案,读者将获得编写更加健壮Java应用程序的实用知识。 --- ####
|
18天前
|
运维 Java 编译器
Java 异常处理:机制、策略与最佳实践
Java异常处理是确保程序稳定运行的关键。本文介绍Java异常处理的机制,包括异常类层次结构、try-catch-finally语句的使用,并探讨常见策略及最佳实践,帮助开发者有效管理错误和异常情况。
61 4