Java后端面试题总结(模拟面试备春招)

Java基础

  1. 重载和重写的区别

重载:同一个类里,方法名相同,return类型,参数列表可以不同
重写:主要发生在继承里,将父类的方法进行扩展。

  1. String StringBuffer StringBuilder的区别是什么
  • 可变性

String底层被final修饰,所以不可变长,每次改变它,都是创建一个新对象去赋值。StringBuilder和StringBuffer都是可变长的。

  • 线程安不安全

String是被final修饰的,不可变,所以安全。
StringBuffer,底层有同步锁修饰方法,线程安全。
StringBuilder,比StringBuffer性能快一点,但是线程不安全。

  1. 自动装箱和拆箱

装箱:将基本类型用它们对应的引用类型包装起来。int->Integer
拆箱:将包装类型转换为基本数据类型。Integer->int

  1. ==与equals区别

==这个符号,在基本数据类型里比的是值,引用数据类型比的是地址。
equals这个符号,单纯比较内容。

  1. 接口和抽象类的区别

(1)接口默认是public修饰,接口里不能写任何方法的实现,抽象类可以
(2)接口的实例变量的默认final修饰,抽象类不一定
(3)接口可以多实现,抽象类只能单继承
(4)interface一个接口,必须实现所有里面的方法,extends一个抽象类,可以选择性重写
(5)接口不能new,但是可以声明,抽象类声明都不行

  1. ArrayList与LinkedList区别

(1)二者都是线程不安全
(2)ArrayList底层是Object数组,LinkedList底层是双向链表
(3)动态数组查找快且方便list.get(int index),增删插慢,链表查找慢,但是增删插快
(4)动态数组整体消耗内存大,链表是每一个node占用大

  1. ArrayList与Vector的区别

Vector类的所有方法都是同步的,可以由两个线程安全地访问一个Vector对象,但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。

ArrayList不是同步的,所以不需要保证线程安全时建议使用ArrayList

  1. HashMap的底层实现

JDK1.8之前,结构是数组+链表,默认长度是16,16指的是数组的长度,每一个区域存储链表的头结点。HashMap通过key的hashCode经过扰动函数处理得到hash值,通过hash算法得到value。

JDK1.8开始,结构是数组+链表+红黑树,默认长度是16,16指的是数组的长度,每一个区域存储链表的头结点。在某一条链表长度超过8的时候,自动由链表转换成红黑树,减少IO次数,提升性能与效率,当链表长度小于6,再从红黑树转回链表。

  1. HashMap与HashTable的区别

HashMap不是线程安全的,因为他的方法不是同步的,所以性能也很高,并且允许在put的时候存放null值。默认容量是16,且在jdk1.8之后有转换成红黑树的机制。HashTable是线程安全的,因为他的方法都是同步的,所以性能很低,但是不允许在put的时候存放null,会引发空指针,默认容量是 11,扩容变2n+1,没有红黑树机制。

  1. HashMap的长度为什么默认是2的幂次方

hash值范围有40亿个数,存放在内存数量太多,所以进行了取模运算(n-1)&hash

  1. HashMap多线程操作导致死循环问题

这个问题只发生在jdk1.8之前。我们知道了resize操作,那么在多线程的时候会有可能导致哈希一致性,成为哈希环,且扩容之后,原node不一定还在原位置。

  1. HashSet与HashMap的区别

实际上,HashSet是根据HashMap设计的。只不过一个是根据key算hashcode,一个是根据成员对象算hashcode,其他只有set都会有的性能问题的区别了。

  1. ConcurrentHashMap和HashTable的区别

从结构上,ConcurrentHashMap在jdk1.8开始有了红黑树的结构,HashTable没有。

从线程安全上,首先这俩都是保证线程安全的,但是保证线程安全的途径不一样。
ConcurrentHashMap在jdk1.4诞生,当时采用的是Segment分段锁,在HashMap的基础上加上锁,由于默认容量为16,所以性能大约是HashTable的16倍。在jdk1.8开始摒弃Segment的概念,使用CAS原子操作保证线程安全(在JDK1.8的时候synchronized已经优化的很好了),而HashTable就是无脑加锁去锁。

线程与锁

  1. 说说自己对synchronized的认识

synchronized主要是为了我们能够保证进行对某项共享资源,多线程的互斥操作,synchronized分为三类,this锁,synchronized同步块,和用synchronized直接修饰方法。

但是如果我们在循环的场景,就不能用this锁了,因为重复加锁去锁,很浪费性能,用synchronized修饰方法最好。

一般我们是直接用this锁的,因为锁的面积越小,出错的概率就越小。

这个synchronized在刚有的时候,是一个很有重量级的锁,因为它的底层是依赖于操作系统的Mutex Lock,而操作系统在内核态到用户态的互相切换,很浪费性能。在之后的优化,将其性能进行了一定的提升,也正是在这个时候,有了偏斜锁,轻量级锁,重量级锁,锁膨胀的概念,让它在不同的环境有了不同的发挥。

  1. 说说synchronized与ReentrantLock的区别

synchronized是一个关键字,在编译的时候会受到操作系统,JVM所影响,而ReentrantLock是.lock包下的一个类。

synchronized的性能一开始不如ReentrantLock,后来因为优化的比较好,出现了无锁,偏斜锁,轻量级锁,重量级锁,和锁膨胀机制之后就比较牛了,超过了。

此外,synchronized有几个功能不如ReentrantLock:

①synchronized不是公平锁,但是ReentrantLock是公平锁,公平锁的意思就是,先来的线程先获得锁,先释放锁。
②synchronized不能实现中断等待,ReentrantLock可以,中断等待,就是让自旋锁这类的,放弃请求。
③当wait的时候,synchronized唤醒锁是靠notify或者notifyAll而且还是单个释放或者全局释放,ReentrantLock靠的是condition进行选择性释放。

  1. 为什么要用线程池

降低资源消耗,方便管理线程,提升响应速度

  1. 实现Runnable接口和Callable接口使用线程池的区别

Runnable接口实现之后无法进行返回值,但是Callable实现之后可以返回值

  1. 创建线程池的方法

(1)使用构造方法创建
在这里插入图片描述
(2)使用Executors实现以下三种ThreadPoolExecutor:

在这里插入图片描述

JUC

  1. 介绍一下Atomic原子类

Atomic也是JUC包下的,具备一定的原子操作,原子操作,用白话来说,就是对于一个线程的操作全程,只要是开始了就不会被其他线程干扰,反过来说,就是保证线程安全。

  1. 介绍一下AtomicInteger

首先这个类也是在JUC的原子类包下的,所以具有着CAS的操作。我们之所以使用CAS就是避免synchronized的高额开销,其原理就是使用CAS+volatile+native。

具体来说,我们的CAS操作,从本质上说,就是使用的Unsafe下的一个native方法获取到原值的地址,去和volatile所修饰的新值(因为volatile保证全局可见直接注入内存)去对比,然后替换旧值,这样就能保证无论怎么样都能获取到最新的值了。

  1. AQS的浅层理解

AQS是比较复杂的东西,我仅仅说说我自己的理解,如果有错误欢迎指出。

AQS是JUC包下一个核心的机制,它是这样设计的:我们有一个共享资源池,如果这个池是空闲的,没有被其他线程获取锁,AQS就可以让一个准备获取锁的线程转换为工作线程去执行,然后将其锁定,不被外来线程所干扰;如果这个池已经被占用了,那么就会进行线程的阻塞或者锁的释放,来让请求的线程获得到锁。

而以上AQS的特征是由CLH队列提供的。

CLH队列是一个双向FIFO队列构成的,说简单点,这个队列如同我们想象的那样,肯定里面是有node 的,node也有pre节点和next节点的,我们最终的目的是让AQS通过CLH获取到线程的同步状态,所以这里就把线程设计成了一个node,加入到队列,让线程之间能够排队去获取共享资源。

并且,为了添加线程到队列是安全的,它还使用到了CAS操作,保证开销小,且安全。再通过内置的一个state方法能监视到线程的实际的状态。

此外ReentrantLock,读写锁,信号量,FutureTask都是基于这种机制。

  1. AQS的深度理解

上面已经说了,我们会使用CLH的队列线程,去访问可能空闲的资源然后绑定,而AQS对于资源的理解,也是有两种方式的:

(1)Exclusive独占:只有一个线程可以去执行,比如ReentrantLock。

(2)Share共享:可以多个线程共同访问,比如CyclicBarrier CountDownLatch 读写锁…

不同的自定义同步器争用共享资源的方式也不同。自定义同步器实现时只需要定义状态的获取和释放,不用维护CLH。

再用口述几个AQS的经典组件:

  • Semaphore(信号量)

和Synchronized的区别在于,信号量指定了一个共享区域允许多个线程共同访问。

  • CountDownLatch(倒计时器)与CyclicBarrier(循环栅栏)

二者的情况其实是差不多的,有一个主线程和若干子线程访问资源的时候,我们可以设置屏障,CountDownLatch的作用是,当左右子线程都经过屏障了之后,主线程,才能从屏障的位置继续执行,也就是被阻塞了,但是子线程不受影响。而CyclicBarrier则是子线程也会受到影响,也就是被阻塞,等子线程都到达了屏障之后,才能与主线程一起重新开始运行。

JVM与GC

  1. 介绍一下JVM内存区域

在Java内存区域中有Java虚拟机栈,堆,程序计数器,本地方法栈,方法区。

其中,线程共享的是堆,方法区,而线程私有的是程序计数器,Java虚拟机栈,本地方法栈。

然后简单介绍一下它们的职责。

  • Java虚拟机栈

Java虚拟机栈里有栈帧,每当一个方法被执行就会产生栈帧,当一个方法被垃圾回收了栈帧就会出栈,所以也是线程私有的,当方法太多了,栈帧就会爆炸,然后oom,但是在爆炸之前,会尝试申请一个新的内存区域防止oom,如果还不够就oom

  • 程序计数器

相对占用较少的内存,并且是线程私有的,目的是通过计数器给线程指引下一个所要执行的字节码指令,本质上属于逻辑计数器,并且只给java方法使用,且不会出现oom

存储所有方法的实例,是JVM里面最大的地方,也是多线程相互共享的一个区域,可以在堆里面进行变量的更新,进行线程的通讯,并且jdk8采用分代回收算法进行垃圾回收,当内存不够,会oom

  • 本地方法栈

native

  • 方法区

也可以叫元数据区,存储方法的常量,静态变量方法,也是线程共享的,实际上也是存储元数据信息。

  1. 常见的垃圾回收算法

标记算法:

  • 引用计数

当一个方法被引用了,进栈,计数器+1,出栈,计数器-1,计数器=0,垃圾回收。

  • 可达性分析

以GC ROOTS为根,将可以调用到的方法串成一条链,不在链上的就回收。

回收算法

  • 标记-清除

用可达性分析标记,不可达清除,如果不额外整理,会产生内存碎片。

  • 复制算法

将堆里面的内存按照比例分成对象面和空闲面,如果对象面的内存满了,就把还活着的放到控线面不断循环,缺点就是,不好划分实际比例,优点就是没碎片,适用于成活率不高的对象。

  • 标记-整理

在标记-清除的基础上,使用更多的性能,将对象平移,把内存碎片的坑填上

  • 分代收集算法

Minor GC:

新生代GC主要用复制算法,因为成活率不高,在新生代的内存区域,又分为Eden区和Survivor区,二者初始大小比例是8:1,复制就是基于这两者进行的。

当经历15代,或者Eden Survivor炸了,或者产生了巨大的对象,在这三种情况下,会进入老年代。

Full GC:

老年代GC主要用标记清除或者标记整理算法。执行效率比Minor GC慢。

  1. 对新生代/老年代垃圾回收器的了解

新生代垃圾回收器:

  • Serial GC:复制算法,单线程最古老的GC,要不回收,要不运行,吞吐量极低。
  • ParNew GC:复制算法,多线程版Serial GC,多核效果明显,唯一可以和CMS相连。
  • Parallel GC:复制算法,运用多线程,分布式的去进行垃圾回收,提升回收效果,减少回收时间,提升吞吐量。

老年代垃圾回收器:

  • Serial Old GC:标记-整理,老年代版Serial GC
  • Parallel Old GC:标记-整理,老年代版Parallel GC
  • CMS GC:标记-清除,采用并发追溯标记,找到新生代到老年代的对象,在程序不终止的情况下去追溯,并在短暂的jvm停止中,快速收集垃圾,并不断循环,会产生oom
  • G1 GC :复制+标记-整理jdk9默认gc,采用分代收集算法
  1. 如何判断对象已经死亡
  2. 介绍一下强引用,软引用,弱引用,虚引用
  3. 如何判断一个类是无用的类

中间件

消息队列

29 用消息队列的好处

主要是异步处理操作提升性能和解耦。

举一个关于异步的简单例子大量数据,实时加载进模型进行数据分析,并且将分析的结果回传给前端去展示,如果是同步的情况,就会很慢,但是异步就可以边加载边计算边展示。

在前后端开发的时候,如果用户的数据直接写入数据库,就会有很大压力,如果我们用消息队列,异步写入数据库,也就不会崩溃了,而且响应快,所以效率也就提升了。但我们也需要考虑其他特殊的因素,比如进入消息队列了,但是从消息队列入库失败了,这就会导致,如果立刻return数据,就会出bug,所以,应该等入库之后再return

还有一点就是解耦,我们将整个项目的资源和使用进程分为消息的发布和订阅模式,就可以让我们在该使用某种数据了之后再去订阅获取。

30 常见的消息队列对比

  • ActiveMQ和RabbitMq消息丢失的概率很低,但是Rocket MQ和Kafka理论不会丢失数据
  • ActiveMQ性能略低于其他三种,并且版本更新也慢。
  • RabbitMq吞吐量略低于Kafka和RocketMq,但是性能很好,如果并发量低于百万级,使用RabbitMq是最好的,但是如果是大数据领域,使用Kafka和RocketMq是最好的
  • RocketMq是阿里开源的。
  • kafka拥有在大数据领域极高的应用,拥有超高的吞吐量和低延迟,唯一的缺点就是会造成消息重复消费。
  1. 使用消息队列可能会带来的问题

系统的可用性降低:引用多用了一种中间价去优化,所以有可能出bug然后就炸了
系统的复杂性提高:我们用消息队列就是为了能让数据不爆炸式的读写,但是如果出现了数据丢失,就是另一回事了
系统一致性问题:分布式环境下,如果消费者没有被真正消费的消息没有同步就会出问题

  1. RabbitMq是如何保证数据一致性的

(1)就如我们上面所说的那样,有可能消费者还没有真正消费,数据就消失的情况,所以我们这里可以引用事务的概念,当程序真正运行完成之后,我们才去提交任务的完成,不然我们就将任务去进行回滚。
(2)可以将没持久消息同步到磁盘上并且当broker宕机后可以去恢复
(3)使用发布方确认,比如我们的消息已经到了broker,但是没有添加进队列,这时候宕机了,就会无法保持一致性。

  1. 如何确保RabbitMq不被重复消费

我能想到的有两种方法,第一种就是用redis的set操作,保证所有操作的唯一性;第二种就是为消息指令添加主键,保证主键唯一,不脏读。

Redis

  1. redis与memcached的区别

(1) redis支持更丰富的数据类型(支持更复杂的应用场景):Redis不仅仅支持简单的kv,还有list set zset hash等数据结构的存储。memcached只支持string
(2)redis支持持久化,memchached不支持
(3)redis有分布式集群一说,memcached只有客户端分布式一说
(4)memchached是使用多线程,非阻塞io的网络模型;Redis是单线程多路复用的IO模型

  1. redis缓存淘汰策略(假如mysql有2kw数据,redis只能存20w,怎么保证redis里的数据都是热点数据)

从redis.conf配置即可,共有6种方案:

(1)volatile-lru:从已设置过期时间的数据集中挑选最近少使用的数据淘汰
(2)从已设置过期施加你的数据集中挑选要过期的数据淘汰
(3)从已设置过期时间的数据集中任意选择数据淘汰
(4)当内存不足以容纳新写入数据的时候,在键空间中,移除最近最少使用的key
(5)从数据集中任意选择数据淘汰
(6)禁止驱逐数据,也就是当内存不足以容纳新写入数据的时候,新写入数据回报错。

  1. redis的缓存持久化

RDB 建立快照,直接复制
AOF appends log

默认使用RDB,也可以结合使用

  1. redis缓存雪崩和缓存穿透问题的解决方案

缓存穿透:绕过缓存,拼命访问数据库。

有两种方法:第一种是把不存在的请求存入缓存,但是过期时间设置短一点。第二种是用位图存储所有可能的请求数据做拦截器

缓存雪崩:大面积缓存过期。

解决方法有三种:第一种是选择缓存持久化快速回复缓存,但是这个方法在恢复的过程中无法存入新的缓存。第二种是本地ehcache缓存+hystrix分布式缓存降级第三种是HA。

  1. 如何解决Redis的并发竞争key

也就是说,多个节点同时对一个key进行操作,最后导致指令重排。

有两种处理方法,但是都是基于分布式锁的。

第一种是使用redis自带的分布式锁,但是会对系统造成一定的性能问题,所以如果没有这种并发竞争key的问题存在,就不要用。

第二种就是使用zk,zk是一种高可靠的资源管理框架。它可以在某个方法上了锁之后,对访问方法的结点进行一个唯一的瞬时有效的数字,然后根据数字去排序获取锁,最后完成业务再释放锁。

  1. 如何保证缓存和数据库双写时候数据的一致性

串行化读写

Nginx

40 简单介绍一下你理解的Nginx

它也是一款轻量级的web服务器,主要功能是反向代理,负载均衡,弹性收缩等服务。简单介绍下

  • 反向代理

首先先说说正向代理,某些情况下,我们需要用户去访问服务器,需要手动设置好服务器的ip和端口,可以拿学校的内网举例,我们想从家连接到学校内网服务器,就需要经历学校vpn才可以。

而反向代理不是给我们代理的,而是代理服务器的,也就是说,我们不需要去和内网服务器去交互,而是直接和代理服务器交互,我们将http请求发给他,他再转发给内网服务器,然后得到结果之后再返回给客户端。

  • 负载均衡

我们应该在学zk,Ribbon的时候就知道这个了,有很多种负载均衡算法,说白了就是分担http请求平均到每一台服务器。

  • 动静分离

其实这一点是很常用的,如果我们每一次都去加载网页的静态资源,很浪费服务器资源,所以就可以让nginx做到将静态部分的页面缓存起来。

  1. Nginx优点是什么

(1)高并发,高性能(其他web服务器不具有)
(2)可以横向扩展(模块化设计,第三方插件齐全)
(3)高可靠性(可以持续运行)
(4)热部署
(5)BSD许可(可以修改源代码,发布我们自定义的nginx使用)

  1. Nginx的构成部分

(1)Nginx二进制可执行文件:由各个模块源码编译出一个文件
(2)Nginx.conf配置文件:控制Nginx行为
(3)access.log:记录所有的HTTP请求
(4)error.log:记录bug

计算机网络

  1. 三次握手,四次挥手

三次握手
四次挥手

  1. TCP与UDP之间的区别

https://blog.csdn.net/qq_41936805/article/details/103499343

  1. HTTP1.0和HTTP1.1的区别

http1.0默认使用短连接,也就是每进行一个http操作就发送一次请求,建立一次连接,任务结束就断开连接,这样太耗资源了。
http1.1开始默认使用长连接,也就是说前后端用于传递http请求数据的tcp连接不会中断。

Mysql

  1. 说说自己对于Mysql的两种存储引擎,MyISAM和InnoDB的理解

①count运算上的区别:因为MyISAM有meta-data,所以全量查询很快,但是INNODB没有
②事务与安全:MyISAM性能会更快,但是不支持事务,INNODB支持事务,所以也支持事务相关的,隔离,回滚,等等的ACID操作
③MyISAM不支持外键,INNODB支持

  1. 解释一下最左前缀原则

mysql的索引可以按照一定的顺序引用多列,这样就可以称之为联合索引。而最左前缀原则指的就是,如果查询的时候查询条件能精确匹配到所有列,就可以返回值。如果匹配顺序不同也可以,但是如果匹配项少,就匹配不到了。

由于最左前缀原则,在创建联合索引的时候,索引字段的顺序需要考虑字段值去重之后的个数,较多的放在前面。

但是如果使用联合索引,就需要避免冗余索引,因为在大多数情况下能够命中(name)的也能命中(name,age),也就是少的能命中多的。所以应该尽量扩展已有的索引而不是创建新索引。

  1. 解释一下b与b+的区别

首先这两者都是m叉查找树,二者都是为了减少IO次数,而被制作出来,去搜索数据的,因为IO对于资源消耗很多,所以为了减少消耗资源,提升速度,就有了这两者,但是也是有显著的区别的。

①B+树比B树更矮,所以IO次数更少
②B+树的查询效率更加高
③B+树更加有利于对数据库进行扫描
④B+树的非叶子节点不存数据,只充当索引,而B树非根子节点都会存储数据

  1. 如果mysql一张表记录过大,那么数据库的crud性能会明显下降,这时候该怎么办呢

①限定数据的范围:对于where条件,一定要有所指定,最好不要全量查询。
②读写分离:分库之后,主库负责写,从库负责读
③垂直分区:根据数据库里面的数据表按照相关性拆分。也就是列的拆分。
④水平分区:保持基本的数据结构,对数据分片,然后切分到不同的数据库或者表,可理解为分布式,这样改动比较简单,但是分库才能体现。只不过在写事务的时候会很复杂。最好在客户端进行拆分。

  1. 事务的理解,什么是事务

首先,事务具有四大特性:ACID

原子性:要么全做,要么全不做。
一致性:执行事务前后,数据保持一致。
隔离性:事务是单独执行的,不会被其他事务干扰。
持久性:一个事务被提交之后,改变的持久的。

  1. 并发事务带来的问题
  • 脏读:读到了下一毫秒就更新的数据
  • 丢失修改:两个事务同时修改数据,导致其中一个修改被覆盖。
  • 不可重复读:循环读数据的过程中,另一个事务修改数据,导致两次读的不一样。
  • 幻读:循环读的过程中,插入了新数据,导致两次读的不一样。与不可重复读区别就在于,一个是insert一个是update
  1. 对事务隔离级别的认识

sql标准定义了四个隔离级别:

①read-uncommitted(读未提交):会造成脏读,幻读,不可重复读
②read-committed(读已提交):会造成幻读和不可重复读
③repeatable-read(可重复读):会造成幻读,是mysql的默认隔离级别.
④serializable(可串行化):什么都不会发生

详情也可以看这篇文章:https://blog.csdn.net/qq_41936805/article/details/103175124

发布了296 篇原创文章 · 获赞 53 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_41936805/article/details/103501438