1 Java 底层知识
2 设计模式
3 网络编程知识
3.1 tcp、udp、http、https 等常用协议
3.2 三次握手与四次关闭
-
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,才能发送FIN报文,因此不能一起发送。故需要四步握手。 -
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。 -
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。 -
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
参考链接:TCP的三次握手与四次挥手理解及面试题(很全面)
3.3 流量控制和拥塞控制
-
流量控制
- 那就是如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。
- TCP的流量控制是利用滑动窗口机制实现的,对TCP报文段进行编号,接收方在返回的ACK中会包含自己的接收窗口的大小,以控制发送方的数据发送。 拥塞控制
- 网络中的链路容量、交换结点中的缓存、处理机等等都有着工作的极限,当网络的需求超过它们的工作极限时,就出现了拥塞。 拥塞控制就是防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。 区别
- 流量控制涉及到的是端对端,主要防止接收端接收速度慢于发送端发送速度,导致数据报文丢失;而拥塞控制涉及整个网络状态,是一个全局的过程,包括整个链路中的交换机,网关等网络状态。
- 慢开始(Slow-Start)和拥塞避免(Congestion Avoidance)结合
- 快重传(Fast Retransmit)和快恢复(Fast Recovery)结合
3.4 网络模型
-
OSI七层模型
- OSI(Open System Interconnect),即开放式系统互联。 一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互连模型。
-
OSI七层模型的划分:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
TCP/IP五层网络
- 五层网络划分: 应用层、传输层、网络层、数据链路层、物理层
- TCP/IP与OSI最大的不同在于OSI是一个理论上的网络通信模型,而TCP/IP则是 实际运行的网络协议。
3.5 TCP 粘包与拆包
假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况:
(1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
(2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
(3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
(4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。
粘包、拆包发生原因:
1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。
3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
等等
解决方案:
- 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
- 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
- 可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。
3.6 http 中 get 和 post 区别
GET参数通过URL传递,POST放在Request body中。
HTTP的底层是TCP/IP。所以GET和POST的底层也是TCP/IP,也就是说,GET/POST都是TCP链接。所以GET、POST本质是相同的;但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
-
重大区别
- GET产生一个TCP数据包;POST产生两个TCP数据包。
- 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
详细介绍:99%的人都理解错了HTTP中GET与POST的区别
3.7 Java RMI
- RPC:Remote Procedure Call,它是一种进程间通信方式;允许像调用本地服务一样调用远程服务。
- RMI:Remote Method Invocation,能够让在某个java虚拟机上的对象像调用本地对象一样调用另一个java 虚拟机中的对象上的方法。即在RPC的基础上有向前迈进了一步,提供分布式对象间的通讯。
3.8 进程通信方式
- 管道
半双工通信,只能在具有亲缘关系的进程间使用;进程间亲缘关系一般指父子进程关系,使用fork创建子进程。 - FIFO(命名管道)
半双工通信,允许无亲缘关系的进程间通信。 - 消息队列
消息队列,是消息的链接表,存放在 内核中。一个消息队列由一个标识符(即队列ID)来标识。 - 信号量
信号量是一个控制资源访问的标识符,信号量本身为一种计数器,用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据。 - 共享内存
共享内存就是两个进程同时共享一块内存,然后在这块内存上的数据可以共同修改和读取,达到通信的目的;共享内存是最快的ipc方式;共享内存常与信号量进行配合使用,通过信号量能实现锁以及著名的pv操作等,主要是用来实现进程间同步。
4框架知识
4.1 Servlet
4.1.1 Servlet的生命周期
包含了下面4个阶段:
- 加载和实例化
当Servlet容器启动或客户端发送一个请求时,Servlet容器会查找内存中是否存在该Servlet实例,若存在,则直接读取该实例响应请求;如果不存在,就创建一个Servlet实例。 - 初始化
实例化后,Servlet容器将调用Servlet的init()方法进行初始化(一些准备工作或资源预加载工作)。 - 请求处理
初始化后,Servlet处于能响应请求的就绪状态。当接收到客户端请求时,调用service()的方法处理客户端请求,HttpServlet的service()方法会根据不同的请求 转调不同的doXxx()方法。 - 服务终止
当Servlet容器关闭时,Servlet实例也随时销毁。其间,Servlet容器会调用Servlet 的destroy()方法去判断该Servlet是否应当被释放(或回收资源)。
4.1.2 Servlet的线程安全
-
变量的线程安全
- 使用全局变量时,多个线程对变量做了更改,当底层调度程序在某个进程还没执行完时,CPU轮转到了另一个进程,这时的全局变量并不是该进程的需要的值。
- 如何防止变量的线程安全:1) 把全局变量改为局部变量; 2) 使用synchronized对doGet进行同步;3) 如果是静态资源则加上final表示这个资源不可以改变。 属性的线程安全
- 在Servlet中可以访问保存在 ServletContext, HttpSession, ServletRequest对象中的属性,这三种对象都提供了getAttribute(),setAttribute() 方法用来对取和设置属性。
详细介绍:javaweb回顾第六篇谈一谈Servlet线程安全问题
4.2 Hibernate
4.2.1 OR Mapping
对象关系映射,是一种为了解决面向对象与关系型数据库存在的互不匹配的技术。
简单说:ORM是通过使用描述对象和数据库之间的映射的元数据,将Java程序中的对象 自动持久化到关系数据库中。
本质上就是讲数据从一种形式转换成另外一种形式。
4.2.2 Hibernate懒加载
即延迟加载,它在查询的时候不会立刻访问数据库,而是返回代理对象,当真正去使用对象的时候才会访问数据库。
fetch 指定了关联对象抓取的方式,参数值常见是select和join,默认是select, select方式先查询主对象,再根据关联外键,每一个对象发一个select查询,获取关联的对象,形成了n+1次查询;而join方式,是left outer join查询,主对象和关联对象用一句外键关联的sql同时查询出来,不会形成多次查询。
参考链接:hibernate懒加载
4.2.3 Hibernate 的缓存机制
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源(数据库)访问的频次,减少响应时间,提高应用程序的效率。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
缓存的好处是降低了数据库的访问次数,提高应用性能,减少了读写数据的时间
-
三种数据持久状态
- 瞬时状态:刚创建的对象还没有被Session持久化、缓存中不存在这个对象的数据并且数据库中没有这个对象对应的数据为瞬时状态这个时候是没有OID。
- 持久状态:对象经过Session持久化操作,缓存中存在这个对象的数据为持久状态并且数据库中存在这个对象对应的数据为持久状态这个时候有OID。
- 游离状态:当Session关闭,缓存中不存在这个对象数据而数据库中有这个对象的数据并且有OID为游离状态。
OID为了在系统中能够找到所需对象,我们需要为每一个对象分配一个唯一的表示号。在关系数据库中我们称之为关键字,而在对象术语中,则叫做对象标识(Object identifier-OID).通常OID在内部都使用一个或多个大整数表示,而在应用程序中则提供一个完整的类为其他类提供获取、操作。

-
缓存级别
-
一级缓存(Session缓存)
由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存。Session级缓存是必需的,不允许而且事实上也无法卸除。在Session级缓存中,持久化类的每个实例都具有唯一的OID。 -
二级缓存(SessionFactory缓存)
由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。
二级缓存是可选的,是一个可配置的插件,默认下SessionFactory不会启用这个插件。 -
查询缓存
hibernate的查询缓存是主要是针对普通属性结果集的缓存, 而对于实体对象的结果集只缓存id。
详细介绍:Java三大框架之——Hibernate中的三种数据持久状态和缓存机制
个人理解
-
在不同等级的缓存中,一级缓存应该是存在于内存之中,当应用程序对一个对象数据进行持久化,肯定会通过hibernate的持久化操作,这时候肯定是转化成了hibernate能操作的对象,这样的对象肯定是存放在hibernate的一级缓存中,换个说法,一级缓存是hibernate的操作对象存储位置
如果开启了二级缓存,则数据都会根据一定策略放进二级缓存中,这个里面记录的是对象的全量数据。
而查询缓存则存放的是主键值,如id,即key值,每次查询时根据id检索缓存中是否有对应数据,没有就从数据库中查询。
4.2.4 Hibernate / Ibatis / MyBatis 之间的区别
详细介绍链接:hibernate跟Mybatis/ ibatis 的区别,为什么选择?
4.3 Spring
-
Spring 特性
- IoC 控制反转
- DI 依赖注入
简易明了的介绍:什么是IOC(控制反转)、DI(依赖注入)
4.3.1 Bean初始化
4.3.2 AOP 原理
4.3.3 Spring 四种依赖注入方式
- set方式
- 构造器
- 静态工厂和实例工厂注入
- 注解注入
使用@Resource或者@Autowired;
@Autowired默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean时,才会按类型装配。
4.4 Spring MVC
4.4.1 MVC
MVC是一个架构,或者说是一个设计模式,它就是强制性使应用程序的输入,处理和输出分开。将一个应用程序分为三个部分:Model,View,Controller。

4.4.2 Spring Boot
-
起步依赖
- spring-boot-starter-xxx就是SpringBoot的起步依赖,SpringBoot通过提供众多起步依赖 降低项目依赖的复杂度。起步依赖本质上是一个 Maven项目对象模型,定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。很多起步依赖的命名都暗示了他们提供的某种或某类功能。 自动配置
-
Spring Boot的自动配置是应用程序启动时,spring boot框架自动检测 classpath里的
Bean来进行配置的一种机制。
在配置类里使用@EnableAutoConfiguration 或者@SpringBootApplication注解 开启组件扫描和自动配置。 通过@SpringBootApplication的exclude参数关闭特定 的自动配置。@SpringBootApplication(exclude = XAutoConfiguration.class)
自动配置的关键几步以及相应的注解总结如下:
-
- @Configuration&与@Bean->基于java代码的bean配置
-
- @Conditional->设置自动配置条件依赖
-
- @EnableConfigurationProperties与@ConfigurationProperties->读取配置文件转换为bean。
-
- @EnableAutoConfiguration、@AutoConfigurationPackage 与@Import->实现bean发现与加载。
详细介绍:深入springboot原理——一步步分析springboot启动机制(starter机制)


7209

被折叠的 条评论
为什么被折叠?



