面经总结:JAVA后端开发

12 篇文章 0 订阅

看了这么多面经,有很多问题答案并不统一,这里列举了一些答案比较有歧义的,并且分享一下我认为正确的答案,希望下个月的面试顺利。

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动态代理(适用于对实现了接口的类的代理或直接代理接口)
  1. 定义一个接口及其实现类;
  2. 自定义类实现 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException

  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
2.CGLIB动态代理(用继承,适用于未实现借口的类的代理)
  1. 定义一个类;
  2. 自定义类实现 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable

  3. 通过 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();
    }
}

ArrayList扩容机制

https://snailclimb.gitee.io/javaguide/#/docs/java/collection/ArrayList源码+扩容机制分析?id=_321-先来看-add-方法
add()将指定的元素追加到此列表的末尾->
ensureCapacityInternal(size + 1)获取默认的容量和传入参数的较大值->
ensureExplicitCapacity(int minCapacity)决定是否扩容->
grow()扩容1.5倍->
hugeCapacity()如果已经超出MAX_ARRAY_SIZE

JPA和MyBatis的区别

MyBatis可以进行更细致的SQL优化,查询必要的字段,但是需要维护SQL和查询结果集的映射。数据库的移植性较差,针对不同的数据库需要编写不同的SQL。
Spring JPA Hibernate对数据库提供了比较完整的封装,封装了基本的DAO操作,有较好的数据库移植性,开发难度高。

如何保证线程安全?

1.同步阻塞 synchronized锁
2.非同步阻塞 CAS乐观锁
3.线程私有变量ThreadLocal,根本不存在竞争资源,必然能保证线程安全。

加锁的方式有哪些?

synchronized同步锁
ReentrantLock实现类
wait/notify

如何在Java程序运行时不停机动态加载一个函数进来?

利用反射机制。反射:在程序运行期间,通过字节码文件获取一个类的方法和属性,或者获取、调用一个对象的方法和属性。

volatile底层原理

volatile是用来同步属性的关键字,可以保证并发编程中的可见性和有序性,是轻量级的锁;
底层方面通过反编译发现用volatile修饰的属性被加上了ACC_VOLATILE标识
synchronized底层:修饰代码块时在方法块前后加上MONITOR_ENTER和MONITOR_EXIT,修饰方法时在方法前加上ACC_SYNCHRONIZED

JDK1.6以后锁机制优化

无状态锁->偏向锁->轻量级锁->重量级锁
偏向锁:在对象头markword中加上线程id,如果每次都只有这个线程访问就让他访问,如果有其他线程id过来锁升级到轻量级锁,多个线程CAS争抢锁,CAS反复自旋,争抢不到锁,锁升级到重量级锁

ReentrantLock底层原理

ReentrantLock使用aqs框架实现,实际上就是围绕着自旋,park-unpark,CAS构建的
公平锁的lock():tryAcquire:获取state量,若为0说明可获得锁,判断!hasQueuedPredecessors即不需要排队,如果CAS能够成功,获取锁;如果为1,判断持有的线程是不是当前线程,如果是就可重入,state+,如果不是当前线程,获得锁失败,进入等待队列(acquireQueued(addWaiter(Node.EXCLUSIVE)),如果前一个节点是头节点先自旋一次看能不能获得锁(state=0就能获得),不能的话park自己;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值