看了这么多面经,有很多问题答案并不统一,这里列举了一些答案比较有歧义的,并且分享一下我认为正确的答案,希望下个月的面试顺利。
TCP三次握手
为什么三次握手
保证客户端到服务端以及服务端到客户端之间都能成功发送接收数据,以防出现客户端发送给服务端连接请求因网络原因没有送达,超时后客户端重新发送一个连接请求,而此时服务端接收并确认,建立连接后传输数据释放连接,之后收到了超时的连接请求,导致误认为接收到的是新的连接请求,从而打开连接不停的等待客户端发送数据。
为什么四次挥手
第一次挥手客户端发送给服务端FIN标志,表示客户端想要断开链接,服务端返回ACK=FIN+1标志表示确认断开,此时客户端到服务端的数据传输已经断开,但是服务端可能还有数据没有传送完成,于是等传送完成后,服务端发送FIN,客户端接受并发送ACK表示服务端到客户端的链接也已断开。
为什么等待2MSL
因为客户端最后一次ACK请求可能会丢失,等待2MSL是为了丢失之后服务端重传关闭请求,客户端重新响应发送ACK,否则服务器端到客户端的连接就无法关闭。
拥塞处理:
四个算法
慢开始:由1开始指数增长直到慢开始阈值,
拥塞避免:从慢开始阈值加法增长直到出现网络拥塞->窗口大小改为1,阈值变为一半重新启用慢开始,
快重传:收到失序报文后重复确认,一旦收到连续三个重复确认,立即重传未收到的报文
快恢复:把阈值变为一半,窗口大小变成阈值大小->进行加法增长(即使用拥塞避免算法)
具体内容详见https://www.cnblogs.com/losbyday/p/5847041.html
索引
索引用来加快MySQL的检索速度。
MySQL索引使用的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结 构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最 快;其余大部分场景,建议选择BTree索引。
MySQL的BTree索引使用的是B树中的B+Tree,但对于主要的两种存储引擎的实现方式是不同 的。
MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的 值为地址读取相应的数据记录。这被称为“非聚簇索引”。
InnoDB: 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其 表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记 录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚 簇索引(或聚集索引)”。而其余的索引都作为助索引,助索引的data域存储相应记录主 键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所 在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索 引。 因此,在设计表的时候,不建议使用过⻓的字段作为主键,也不建议使用非单调的字段 作为主键,这样会造成主索引频繁分裂。 PS:整理自《Java工程师修炼之道》
参考:https://blog.csdn.net/weixin_41835916/article/details/81569089
事务
事务是逻辑上的一组操作,要么都执行,要么都不执行。
ACID
并发事务会带来的问题以及对应隔离级别解决方案:脏读(读取已提交),丢失修改,不可重复读(可重复读),幻读(串行化)
CAP理论
指的是在分布式系统中C(Consistency一致性)同一时间所有节点数据相同;A(Available可用性)用户访问时在正常响应时间内返回结果;P(Partition Tolerance分区容错性)遇到节点或分区故障时仍能提供正常服务;不可兼得
参考http://c.biancheng.net/view/6493.html
Cache和Buffer
cache指的是缓存,buffer指的是缓冲。
cache针对资源调用的空间局部性,Cache会把最近可能访问的资源放在里面,访问Cache中的资源会相对快一些。
buffer针对的是生产者消费者对资源生产/消费的速率不一致问题。
内存碎片
外部碎片:还没有被分配出去,但因为太小了无法分配给申请内存空间的内存空闲区域
内部碎片:已经被分配出去但无法被利用的内存空间
内部碎片的产生:因为所有的内存分配必须起始于可被 4、8 或 16 整除(视处理器体系结构而定)的地址或者因为MMU的分页机制的限制,决定内存分配算法仅能把预定大小的内存块分配给客户。假设当某个客户请求一个43字节的内存块时,因为没有适合大小的内存,所以它可能会获得 44字节、48字节等稍大一点的字节,因此由所需大小四舍五入而产生的多余空间就叫内部碎片。
外部碎片的产生:频繁的分配与回收物理页面会导致大量的、连续且小的页面块夹杂在已分配的页面中间,就会产生外部碎片。假设有一块一共有100个单位的连续空闲内存空间,范围是099。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为09区间。这时候你继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为1014区间。如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是09空闲,1014被占用,1524被占用,2599空闲。其中09就是一个内存碎片了。如果1014一直被占用,而以后申请的空间都大于10个单位,那么09就永远用不上了,变成外部碎片。
垃圾回收机制
JavaGuideV4.0 P105
关于垃圾收集器的7种还是看这个链接吧,JavaGuide写的有些偏差https://www.cnblogs.com/cxxjohnson/p/8625713.html
Linkedhashmap的实现
首先了解HashMaphttps://blog.csdn.net/justloveyou_/article/details/62893086
Lru的实现(其实就是实现LinkedHashMap)
BIO,NIO,AIO
https://www.cnblogs.com/javaguide/p/io.html
BIO表示同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO表示同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO表示异步非阻塞IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成IO操作后再通知服务器应用来启动线程进行处理。
应用场景:
-
BIO适用于连接数目比较小且固定的架构,该方式对服务器资源要求比较高,JDK 1.4以前的唯一选择。
-
NIO适用于连接数目多且连接比较短(轻操作)的架构,如聊天服务器,编程复杂,JDK 1.4开始支持,如在Netty框架中使用。
-
AIO适用于连接数目多且连接比较长(重操作)的架构,如相册服务器,充分调用操作系统参与并发操作,编程复杂,JDK 1.7开始支持。
备注:在大多数场景下,不建议直接使用JDK的NIO类库(门槛很高),除非精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,可以使用NIO框架Netty来进行NIO编程,其既可以作为客户端也可以作为服务端,且支持UDP和异步文件传输,功能非常强大。
动态代理
1.JDK动态代理(适用于对实现了接口的类的代理或直接代理接口)
-
定义一个接口及其实现类;
-
自定义类实现
InvocationHandler
并重写invoke
方法,在invoke
方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException
-
通过
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法创建代理对象;
2.CGLIB动态代理(用继承,适用于未实现借口的类的代理)
-
定义一个类;
-
自定义类实现
MethodInterceptor
并重写intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的invoke
方法类似;public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable
-
通过
Enhancer
类的create()
创建代理类;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class DebugMethodInterceptor implements MethodInterceptor {
/**
* @param o 被代理的对象(需要增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
*/
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//调用方法之前,我们可以添加自己的操作
System.out.println("before method " + method.getName());
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
System.out.println("after method " + method.getName());
return object;
}
}
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new DebugMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}